Пример #1
0
class ModelThread(threading.Thread):
    def __init__(self,
                 event_queue,
                 paint_queue,
                 time_offset,
                 init_data=None,
                 init_connection=None):
        super(ModelThread, self).__init__()

        # Queues
        self.event_queue = event_queue
        self.paint_queue = paint_queue

        #Event handling
        self.handlers = {}
        self.initialize_handlers()

        # Unique uuid identifying clients in the network
        self.uuid = uuid.uuid4().hex
        # Flag indicating weather we want to enter critical section when the token comes
        self.want_to_enter_critical_section = False
        # Information about critical section like the timestamp and client uuid which is in the section
        self.critical_section = None
        # Time offset between ntp server and local time
        self.time_offset = time_offset
        # Value of the last token we have received
        self.last_token = None

        # Initial board state
        self.board_state = [[
            0 for _ in range(config.getint('Tkinter', 'CanvasY'))
        ] for _ in range(config.getint('Tkinter', 'CanvasX'))]

        # If we are the first client
        if not init_data:
            self.next_hop_info = None
            self.next_next_hop_info = None
            self.sending_queue = None
            self.message_sender = None
            self.predecessor = None
            self.last_token = 0
        else:
            self.next_hop_info = init_data['next_hop']
            if not init_data['next_next_hop']:
                # If there is no next_next_hop init data in the response we are the second client so we set
                # next next hop as our address
                self.next_next_hop_info = (helpers.get_self_ip_address(),
                                           config.getint(
                                               'NewPredecessorListener',
                                               'Port'))
            else:
                # If there are more thant two clients we set the value from the response
                self.next_next_hop_info = init_data['next_next_hop']

            # Address of our predecessor
            self.predecessor = init_connection.getsockname()

            # We initialize connection to our next hop and we start sending queue
            ip, port = init_data['next_hop']
            s = socket.create_connection((ip, port))
            self.sending_queue = queue.Queue(maxsize=0)
            self.message_sender = MessageSender(self.event_queue,
                                                self.sending_queue, s)
            self.message_sender.start()
            self.initialize_board(init_data['board_state'])
            if init_data['critical_section_state']:
                self.critical_section = init_data['critical_section_state']
                self.paint_queue.put({'type': DrawingQueueEvent.BOARD_CLOSED})

            # We signal that client has initialized properly
            init_connection.shutdown(1)
            init_connection.close()

        # We start a dummy message sender event which will create dummy messages to detect connection breaks
        self.dummy_message_sender = DummyMessageSender(self.event_queue,
                                                       self.uuid)
        self.dummy_message_sender.start()

    def run(self):
        while True:
            (e) = self.event_queue.get()
            handler_function = self.handlers[type(e).__name__]
            handler_function(e)

    def initialize_board(self, board_state):
        for counter in (len(board_state)):
            x_coord, y_coord = board_state[counter]
            try:
                self.board_state[x_coord][y_coord] = 1
            except IndexError:
                return
        self.paint_queue.put({
            'type': DrawingQueueEvent.DRAWING,
            'data': (board_state, 1)
        })

    def initialize_handlers(self):
        # Inner Handlers
        self.handlers[
            'InnerNewClientRequestEvent'] = self.handle_new_client_request
        self.handlers[
            'InnerNewPredecessorRequestEvent'] = self.handle_new_predecessor_request_event
        self.handlers[
            'InnerDrawingInformationEvent'] = self.handle_inner_draw_information_event
        self.handlers[
            'InnerWantToEnterCriticalSection'] = self.inner_handle_want_to_enter_critical_section_event
        self.handlers[
            'InnerLeavingCriticalSection'] = self.inner_leaving_critical_section_event
        self.handlers['InnerNextHopBroken'] = self.inner_next_hop_broken_event

        # Outter handlers
        self.handlers[
            'DrawingInformationEvent'] = self.handle_drawing_information_event
        self.handlers[
            'EnteredCriticalSectionEvent'] = self.handle_entering_critical_section
        self.handlers[
            'LeavingCriticalSectionEvent'] = self.handle_leaving_critical_section
        self.handlers['TokenPassEvent'] = self.handle_token_pass_event
        self.handlers['NewNextNextHop'] = self.handle_new_next_next_hop_event
        self.handlers[
            'TokenReceivedQuestionEvent'] = self.handle_token_received_question_event
        self.handlers['DummyMessageEvent'] = self.handle_dummy_message_event

    ############################################################################################
    #
    #                                      Inner Event handlers
    ############################################################################################
    def handle_inner_draw_information_event(self, event):
        def draw_points(event):
            color = event.data['color']
            points = event.data['points']
            try:
                for point in points:
                    x, y = point
                    self.board_state[x][y] = color
            except IndexError as e:
                return

            self.paint_queue.put({
                'type': DrawingQueueEvent.DRAWING,
                'data': (points, color)
            })
            if (self.sending_queue):
                self.sending_queue.put(
                    events.DrawingInformationEvent(
                        self.uuid, helpers.get_current_timestamp(), points,
                        color))

        if not self.critical_section:
            draw_points(event)
        elif self.critical_section['timestamp'] > event.data['timestamp']:
            draw_points(event)
        elif self.critical_section['client_uuid'] == self.uuid:
            draw_points(event)
        elif self.critical_section['client_uuid'] != self.uuid:
            pass

    def inner_handle_want_to_enter_critical_section_event(self, _):
        self.want_to_enter_critical_section = True

    def inner_leaving_critical_section_event(self, _):
        self.critical_section = None
        self.paint_queue.put({"type": DrawingQueueEvent.BOARD_OPEN})
        if self.sending_queue:
            self.sending_queue.put(
                events.LeavingCriticalSectionEvent(
                    helpers.get_current_timestamp(), self.uuid))
            self.sending_queue.put(events.TokenPassEvent(self.last_token))
        else:
            self.event_queue.put(
                events.LeavingCriticalSectionEvent(
                    helpers.get_current_timestamp(), self.uuid))
            self.event_queue.put(events.TokenPassEvent(self.last_token))

    def inner_next_hop_broken_event(self, _):
        # If we detect that the next hop connection is down we want to:
        # 1.Try to reconnect to the client
        # 2.If reconnect fails we want to connect to our next next hop
        # 3.When we succesfully connect to our next next hop we want to send recovery token question
        #   in case that the dead client was holding the token the moment he died
        ip, port = self.next_next_hop_info
        # If we are the only client left we reset the data to the initial state
        if ip == helpers.get_self_ip_address():
            self.critical_section = None
            self.next_hop_info = None
            self.next_next_hop_info = None
            if self.message_sender:
                self.message_sender.stop()
            self.sending_queue = None
            self.message_sender = None
            self.predecessor = None
            self.last_token = 0
            self.paint_queue.put({'type': DrawingQueueEvent.BOARD_OPEN})
            return

        def connect_to_next_next_hop(self):
            ip, port = self.next_next_hop_info
            try:
                s = socket.create_connection((ip, port))
                self.sending_queue = queue.Queue(maxsize=0)
                self.message_sender = MessageSender(self.event_queue,
                                                    self.sending_queue, s)
                self.message_sender.start()
                self.next_hop_info = self.next_next_hop_info
                # After we connect to a new client we have to check whether the dead client wasn't in posession
                # of token
                self.sending_queue.put(
                    events.TokenReceivedQuestionEvent(self.last_token))
            except Exception as e:
                logger.error(e)

        ip, port = self.next_hop_info
        try:
            s = socket.create_connection((ip, port))
            self.sending_queue = queue.Queue(maxsize=0)
            self.message_sender = MessageSender(self.event_queue,
                                                self.sending_queue, s)
            self.message_sender.start()
        except ConnectionRefusedError as e:
            logger.error(e)
            connect_to_next_next_hop(self)

    ############################################################################################
    #
    #                                      Event handlers
    ############################################################################################
    def handle_new_client_request(self, event):
        # At first we want to receive information to properly connect as new predecessor after sending init_data
        message_size = event.data['connection'].recv(8)
        message_size = int.from_bytes(message_size, byteorder='big')
        message = b''
        while len(message) < message_size:
            packet = event.data['connection'].recv(message_size - len(message))
            if not packet:
                return None
            message += packet
        client_request = json.loads(message.decode('utf-8'))

        first_client = not self.next_hop_info
        # When we detect a new client connecting we want to;
        # 1.Send him the initial data over the connection we already established
        # 2.Connect to him as a predecessor

        # Gather the initial board state (only the coloured spots)
        marked_spots = [(x, y) for x in range(len(self.board_state))
                        for y in range(len(self.board_state[x]))
                        if self.board_state[x][y] == 1]
        # If we have next hop information we send it, if we do not have we are the first client so we send our
        # information as the first hop information
        next_hop = (helpers.get_self_ip_address(),
                    config.getint(
                        'NewPredecessorListener',
                        'Port')) if first_client else self.next_hop_info
        # If we are the first client next next hop is None
        response = events.NewClientResponseEvent(next_hop,
                                                 self.next_next_hop_info,
                                                 marked_spots,
                                                 self.critical_section)
        message = helpers.event_to_message(response)
        message_size = (len(message)).to_bytes(8, byteorder='big')
        event.data['connection'].send(message_size)
        event.data['connection'].send(message)

        try:
            message = event.data['connection'].recv(8)
        except Exception as ex:
            if message == b'':
                # Only case when we have a succesfull read of 0 bytes is when other socket shutdowns normally
                pass
            else:
                logger.error(ex)
                #Client did not initializ correctly so we abort the process
                return
        # If we are not the first client we have to update our next next hop to our previous next hop
        if not first_client:
            self.next_next_hop_info = self.next_hop_info
        else:
            # If we are the first client we update our next next hop info to self address
            self.next_next_hop_info = (helpers.get_self_ip_address(),
                                       config.getint('NewPredecessorListener',
                                                     'Port'))

        # We stop current message sender if it exists
        if self.message_sender:
            self.message_sender.stop()

        # We update our next hop info with the newest client request
        self.next_hop_info = client_request['data']['address']
        ip, port = self.next_hop_info
        # We establish a new connection and a new message sender
        connection = socket.create_connection((ip, port), 100)
        self.sending_queue = queue.Queue(maxsize=0)
        self.message_sender = MessageSender(self.event_queue,
                                            self.sending_queue, connection)
        self.message_sender.start()
        if first_client and self.last_token != None:
            # If we are the first client we start passing of the token
            self.sending_queue.put(events.TokenPassEvent(self.last_token))

    def handle_drawing_information_event(self, event):
        def draw_point(event):
            points = event.data['points']
            color = event.data['color']
            try:
                for point in points:
                    x, y = point
                    self.board_state[x][y] = color
            except IndexError:
                return

            self.paint_queue.put({
                'type': DrawingQueueEvent.DRAWING,
                'data': (points, color)
            })
            if self.sending_queue:
                self.sending_queue.put(event)

        if event.data['client_uuid'] == self.uuid:
            return
        if not self.critical_section:
            draw_point(event)
        elif self.critical_section['timestamp'] > event.data['timestamp']:
            draw_point(event)
        elif self.critical_section['client_uuid'] == event.data['client_uuid']:
            draw_point(event)
        elif self.critical_section['client_uuid'] != event.data['client_uuid']:
            pass

    def handle_new_predecessor_request_event(self, event):
        # The moment we have a new predecessor this means that the client before our predecessor
        # has a new next next hop address (which is our address) and our predecessor has new next next hop (which is
        # our next hop)
        self.predecessor = event.data['client_address']
        self_address = (helpers.get_self_ip_address(),
                        config.getint('NewPredecessorListener', 'Port'))
        # Special case if we have only 2 nodes left
        if self.predecessor[0] == self.next_hop_info[0]:
            self.sending_queue.put(
                events.NewNextNextHop(self.predecessor, self_address))
            self.next_next_hop_info = (helpers.get_self_ip_address(),
                                       config.getint('NewPredecessorListener',
                                                     'Port'))
        else:
            # We send information to predecessor of our predecessor about his new next next hop address
            self.sending_queue.put(
                events.NewNextNextHop(self_address, self.predecessor))
            # We send information to our predecessor about his new next next hop
            self.sending_queue.put(
                events.NewNextNextHop(self.next_hop_info, self_address))

    def handle_entering_critical_section(self, event):
        data = event.data
        if (data['client_uuid']) == self.uuid:
            return
        self.critical_section = {
            'timestamp': data['timestamp'],
            'client_uuid': data['client_uuid']
        }
        self.paint_queue.put({'type': DrawingQueueEvent.BOARD_CLOSED})
        self.sending_queue.put(event)

    def handle_leaving_critical_section(self, event):
        data = event.data
        if (data['client_uuid']) == self.uuid:
            return

        if self.critical_section and self.critical_section[
                'client_uuid'] == event.data['client_uuid']:
            self.paint_queue.put({'type': DrawingQueueEvent.BOARD_OPEN})
            self.critical_section = None

        self.sending_queue.put(event)

    def handle_token_pass_event(self, event):
        token = event.data['token'] + 1
        self.last_token = token

        if self.critical_section:
            # If we have received the token and the critical section exists we unvalidate critical secion info
            self.critical_section = None
            self.paint_queue.put({'type': DrawingQueueEvent.BOARD_OPEN})

        if self.want_to_enter_critical_section:
            timestamp = helpers.get_current_timestamp()
            self.critical_section = {
                'timestamp': timestamp,
                'client_uuid': self.uuid
            }
            leave_critical_section_deamon = CriticalSectionLeaver(
                self.event_queue)
            leave_critical_section_deamon.start()
            self.want_to_enter_critical_section = False
            self.paint_queue.put({'type': DrawingQueueEvent.BOARD_CONTROLLED})
            self.sending_queue.put(
                events.EnteredCriticalSectionEvent(timestamp, self.uuid))
        else:
            if self.sending_queue:
                self.sending_queue.put(events.TokenPassEvent(token))

    def handle_new_next_next_hop_event(self, event):
        post_destination_ip, _ = event.data['destination_next_hop']
        next_hop_ip, _ = self.next_hop_info

        # We are the recipient of the message
        if post_destination_ip == next_hop_ip:
            self.next_next_hop_info = event.data['new_address']
        else:
            self.sending_queue.put(event)

    def handle_token_received_question_event(self, event):
        # We check weather the last token we received is greater than
        # the token from the request
        # If it is, this means that the disconnected client was not in posession of the token when he disconnected
        # If it was we have to unvalidate critial secion information and send token further

        if self.last_token > event.data['token'] + 1:
            return
        else:
            self.critical_section = None
            self.paint_queue.put({'type': DrawingQueueEvent.BOARD_OPEN})
            token = event.data['token'] + 1 if event.data[
                'token'] else self.last_token + 1
            self.sending_queue.put(events.TokenPassEvent(token))

    def handle_dummy_message_event(self, event):
        if self.uuid != event.data['uuid']:
            return
        else:
            if self.sending_queue:
                self.sending_queue.put(event)