class Proposer (essential.Proposer):
'''
This class extends the functionality of the essential Proposer
implementation by tracking whether the proposer believes itself to
be the current leader of the Paxos instance. It also supports a flag
to disable active participation in the Paxos instance.
The 'leader' attribute is a boolean value indicating the Proposer's
belief in whether or not it is the current leader. As the documentation
for the Messenger.on_leadership_acquired() method describes multiple
nodes may simultaneously believe themselves to be the leader.
The 'active' attribute is a boolean value indicating whether or not
the Proposer should send outgoing messages (defaults to True). Setting
this attribute to false places the Proposer in a "passive" mode where
it processes all incoming messages but drops all messages it would
otherwise send.
'''
leader = False
active = True
def set_proposal(self, value):
'''
Sets the proposal value for this node iff this node is not already aware of
another proposal having already been accepted.
'''
if self.proposed_value is None:
self.proposed_value = value
if self.leader and self.active:
self.messenger.send_accept( self.proposal_id, value )
def prepare(self, increment_proposal_number=True):
'''
Sends a prepare request to all Acceptors as the first step in
attempting to acquire leadership of the Paxos instance. If the
'increment_proposal_number' argument is True (the default), the
proposal id will be set higher than that of any previous observed
proposal id. Otherwise the previously used proposal id will simply be
retransmitted.
'''
if increment_proposal_number:
self.leader = False
self.promises_rcvd = set()
self.proposal_id = (self.next_proposal_number, self.proposer_uid)
self.next_proposal_number += 1
if self.active:
self.messenger.send_prepare(self.proposal_id)
def observe_proposal(self, from_uid, proposal_id):
'''
Optional method used to update the proposal counter as proposals are
seen on the network. When co-located with Acceptors and/or Learners,
this method may be used to avoid a message delay when attempting to
assume leadership (guaranteed NACK if the proposal number is too low).
'''
if from_uid != self.proposer_uid:
if proposal_id >= (self.next_proposal_number, self.proposer_uid):
self.next_proposal_number = proposal_id.number + 1
def recv_prepare_nack(self, from_uid, proposal_id, promised_id):
'''
Called when an explicit NACK is sent in response to a prepare message.
'''
self.observe_proposal( from_uid, promised_id )
def recv_accept_nack(self, from_uid, proposal_id, promised_id):
'''
Called when an explicit NACK is sent in response to an accept message
'''
def resend_accept(self):
'''
Retransmits an Accept! message iff this node is the leader and has
a proposal value
'''
if self.leader and self.proposed_value and self.active:
self.messenger.send_accept(self.proposal_id, self.proposed_value)
def recv_promise(self, from_uid, proposal_id, prev_accepted_id, prev_accepted_value):
'''
Called when a Promise message is received from the network
'''
self.observe_proposal( from_uid, proposal_id )
if self.leader or proposal_id != self.proposal_id or from_uid in self.promises_rcvd:
return
self.promises_rcvd.add( from_uid )
if prev_accepted_id > self.last_accepted_id:
self.last_accepted_id = prev_accepted_id
# If the Acceptor has already accepted a value, we MUST set our proposal
# to that value. Otherwise, we may retain our current value.
if prev_accepted_value is not None:
self.proposed_value = prev_accepted_value
if len(self.promises_rcvd) == self.quorum_size:
self.leader = True
self.messenger.on_leadership_acquired()
if self.proposed_value is not None and self.active:
self.messenger.send_accept(self.proposal_id, self.proposed_value)