예제 #1
0
class Node:
    """
        Node in the sense of Raymond's algorithm
        Implements the magical algorithm
    """
    def __init__(self, name, neighbors=None):
        self.name = name
        self.holder = None
        self.using = False
        self.request_q = Fifo()
        self.asked = False
        self.is_recovering = False
        self.is_working = False
        self.neighbors_states = {}
        self.neighbors = neighbors if neighbors else []
        self.consumer = Consumer(self.name, self._handle_message)
        self.publisher = Publisher(self.name)

    def _assign_privilege(self):
        """
            Implementation of ASSIGN_PRIVILEGE from Raymond's algorithm
        """
        if self.holder == "self" and not self.using and not self.request_q.empty(
        ):
            self.holder = self.request_q.get()
            self.asked = False
            if self.holder == "self":
                self.using = True
                self._enter_critical_section()
                self._exit_critical_section()
            else:
                self.publisher.send_request(self.holder, MSG_PRIVILEGE)

    def _make_request(self):
        """
            Implementation of MAKE_REQUEST from Raymond's algorithm
        """
        if self.holder != "self" and not self.request_q.empty(
        ) and not self.asked:
            self.publisher.send_request(self.holder, MSG_REQUEST)
            self.asked = True

    def _assign_privilege_and_make_request(self):
        """
            Calls assign_privilege and make_request
            sleep(x) allows to display what is happening
        """
        if not self.is_recovering:
            time.sleep(PROPAGATION_DELAY)
            self._assign_privilege()
            self._make_request()

    def ask_for_critical_section(self):
        """
            When the node wants to enter the critical section
        """
        self.request_q.push("self")
        self._assign_privilege_and_make_request()

    def kill(self):
        """
            Simulates a node crash
            Clears its state
            Then call recover method
        """
        self.holder = None
        self.using = False
        self.is_working = False
        self.request_q = Fifo()
        self.asked = False
        self.neighbors_states = {}
        self._recover()

    def _recover(self):
        """
            Implements Raymond's recovering process
        """
        self.is_recovering = True
        time.sleep(RECOVER_TIMEOUT)
        for neighbor in self.neighbors:
            self.publisher.send_request(neighbor, MSG_RESTART)

    def _receive_request(self, sender):
        """
            When the node receives a request from another
        """
        self.request_q.push(sender)
        self._assign_privilege_and_make_request()

    def _receive_privilege(self):
        """
            When the node receives the privilege from another
        """
        self.holder = "self"
        self._assign_privilege_and_make_request()

    def _enter_critical_section(self):
        """
            Does stuff to simulate critical section
        """
        self.is_working = True
        with open("working_proof.txt", "a") as f:
            f.write(self.name + "\n")
        time.sleep(WORK_TIME)
        self.is_working = False

    def _exit_critical_section(self):
        """
            When the node exits the critical section
        """
        self.using = False
        self._assign_privilege_and_make_request()

    def _handle_message(self, ch, method, properties, body):
        """
            Callback for the RabbitMQ consumer
            Messages are sent with 'node_name.type' routing keys and 'sender' as body
        """
        sender = method.routing_key.split(".")[0]
        message_type = method.routing_key.split(".")[2]
        logging.info("## Received %s from %s" % (message_type, sender))
        if message_type == MSG_REQUEST:
            self._receive_request(sender)
        elif message_type == MSG_PRIVILEGE:
            self._receive_privilege()
        elif message_type == MSG_INITIALIZE:
            self.initialize_network(sender)
        elif message_type == MSG_RESTART:
            self._send_advise_message(sender)
        elif message_type == MSG_ADVISE:
            message = body.decode("UTF-8")
            self._receive_advise_message(sender, message)

    def _receive_advise_message(self, sender, message):
        """
            When the node receives an advise message from another
        """
        state = make_tuple(message)
        self.neighbors_states[sender] = state
        if len(self.neighbors_states) == len(self.neighbors):
            self._finalize_recover()

    def _finalize_recover(self):
        """
            Finalize recovering process
        """
        # Determine holder
        for neighbor, state in self.neighbors_states.items():
            if not state[0]:
                self.holder = neighbor
                break
        if (not self.holder or self.holder
                == "self"):  # Privilege may be received while recovering
            self.holder = "self"
            # Determine asked
            self.asked = False
        else:
            self.asked = self.neighbors_states[self.holder][2]
        # Rebuild request_q
        for neighbor, state in self.neighbors_states.items():
            if state[0] and state[1] and not neighbor in self.request_q:
                self.request_q.push(neighbor)
        self.is_recovering = False
        self._assign_privilege_and_make_request()

    def _send_advise_message(self, recovering_node):
        """
            Sends  X - Y relationship state:
                (HolderY == X, AskedY, X in Request_qY)
        """
        state = (
            self.holder == recovering_node,
            self.asked,
            recovering_node in self.request_q,
        )
        self.publisher.send_request(recovering_node, MSG_ADVISE, str(state))

    def initialize_network(self, init_sender=None):
        """
            When initializing, send initialize messages
            to neighbors BUT the one which sent it to the node (if it exists)
        """
        neighbors = self.neighbors.copy()
        if init_sender:
            neighbors.remove(init_sender)
            self.holder = init_sender
        else:
            self.holder = "self"
        for neighbor in neighbors:
            self.publisher.send_request(neighbor, MSG_INITIALIZE)