示例#1
0
class Messenger:
    def __init__(self, mac, broadcast_address, broadcast_port, tcp_port, node_id=None, loop=None):
        self._mac = mac
        self._node_id = node_id
        self._nodes = {}
        self._lost_nodes = set()

        self._loop = loop or asyncio.get_event_loop()
        self._io_executor = AsyncExecutor(5, self._loop)
        self._logger = logging.getLogger('Messenger')

        self._logger.info('Creating messengers...')
        if tcp_port != 0:
            self._tcp_messenger = TCPMessenger(tcp_port, self._io_executor, loop=self._loop)
        if broadcast_port != 0:
            self._udp_messenger = UDPMessenger(broadcast_address, broadcast_port, self._io_executor, True, self._loop)

        self._listeners = {
            'tcp': self._tcp_messenger,
            'udp': self._udp_messenger,
        }

        for messenger in self._listeners.values():
            messenger.set_on_message(self._on_message)

        self._daemon = None
        self._message_queue = asyncio.Queue()

    def start(self):
        self._logger.info('Starting messengers...')
        for messenger in self._listeners.values():
            messenger.start()
        self._logger.info('Messengers started!')
        self._daemon = wrap_exc(asyncio.async(self._taking_messages_loop()), self._logger)

    def stop(self):
        if self._daemon is not None:
            self._daemon.cancel()
            self._daemon = None
        self._tcp_messenger.stop()
        self._udp_messenger.stop()
        self._io_executor.shutdown(wait=False)

    def _serialize(self, message: Message):
        self._logger.debug('Serialized message: {}...'.format(message.__getstate__()))
        return serialize_message(message, ENCODING)

    @asyncio.coroutine
    def _on_message(self, message: Message, address):
        node_id = message.author_node_id
        if node_id in self._lost_nodes:
            self._lost_nodes.remove(node_id)
            self._logger.info('Heard about lost node again! (node_id={}) (so lost nodes: {})'
                              .format(node_id, self._lost_nodes))
        if node_id not in self.nodes:
            host = address[0]
            self._add_node(node_id, host)
            wrap_exc(asyncio.async(self.send_message(node_id, Message(MessageType.PING, self.node_id))), self._logger)
        elif address[0] != self.nodes[node_id]:
            self._logger.error('Unexpected state! My nodes: {}. {} != {} (node_id={})'
                               .format(self.nodes, address[0], self.nodes[node_id], node_id))

        if message.type == MessageType.TAKE_TOKEN:
            assert isinstance(message, TakeTokenMessage)
            for node_id, host in message.nodes.items():
                if node_id not in self.nodes:
                    self._add_node(node_id, host)
                else:
                    if host != self.nodes[node_id]:
                        self._logger.error('Unexpected state! My nodes: {}. Nodes in message: {}. {} != {} (node_id={})'
                                           .format(self.nodes, message.nodes, host, self.nodes[node_id], node_id))

    def _add_node(self, node_id, host):
        for old_node_id, old_host in self.nodes.items():
            if old_host == host:
                self._logger.error('IP collision! (new node id={}, new host={}, old node id={}, old ip={})'
                                   .format(node_id, host, old_node_id, old_host))
        self.nodes[node_id] = host
        self._logger.info('New node: {} at {}! (total number: {})'.format(node_id, host, len(self.nodes)))

        def format_node_id(node_id):
            return '{}{}'.format(node_id, '' if self.node_id != node_id else ' (me)')
        self._logger.info('Nodes order: {}'.format([(format_node_id(node_id), self.nodes[node_id]) for node_id in sorted(self.nodes.keys())]))

    @asyncio.coroutine
    def _taking_messages_loop(self):
        while True:
            takings = []
            for listener in self._listeners.values():
                takings.append(wrap_exc(asyncio.async(listener.take_message()), self._logger))
            with auto_cancellation(takings):
                done, pending = yield from asyncio.wait(takings, return_when=concurrent.futures.FIRST_COMPLETED)
                for f in done:
                    message = yield from f
                    self._message_queue.put_nowait(message)
                    self._logger.debug(
                        'Message {} putted. Queue size: {}.'.format(message.type, self._message_queue.qsize()))

    @asyncio.coroutine
    def take_message(self):
        message = yield from self._message_queue.get()
        self._logger.debug('Taking message: {}...'.format(message.type))
        return message

    @asyncio.coroutine
    def send_broadcast(self, message):
        message_bytes = self._serialize(message)
        yield from self._udp_messenger.send_message(message_bytes)

    @asyncio.coroutine
    def send_message(self, node_id, message):
        if node_id in self._lost_nodes:
            self._logger.error('Sending message to node, that seems to be lost! (node id={}, message type={})'
                               .format(node_id, message.type))
        self._logger.debug('Sending "{}" message to node {}...'.format(message.type, node_id))
        host = self.nodes[node_id]
        success = yield from self._tcp_messenger.send_message(host, self._serialize(message))
        if not success:
            self._lost_nodes.add(node_id)
            self._logger.warn('Node seems to be lost! (node_id={})'.format(node_id))
            if self.node_id == node_id:
                self._logger.error('We can not connect with our-self?! ')
        return success

    def get_next_available_node_id(self):
        nodes = sorted(self.nodes.keys())
        next_index = (nodes.index(self.node_id) + 1)
        for candidate_id in nodes[next_index:] + nodes[:next_index]:
            if candidate_id not in self._lost_nodes:
                return candidate_id

    @property
    def node_id(self):
        return str(self._node_id or self._mac)

    @property
    def nodes(self):
        return self._nodes
示例#2
0
class Messenger:
    def __init__(self,
                 mac,
                 broadcast_address,
                 broadcast_port,
                 tcp_port,
                 node_id=None,
                 loop=None):
        self._mac = mac
        self._node_id = node_id
        self._nodes = {}
        self._lost_nodes = set()

        self._loop = loop or asyncio.get_event_loop()
        self._io_executor = AsyncExecutor(5, self._loop)
        self._logger = logging.getLogger('Messenger')

        self._logger.info('Creating messengers...')
        if tcp_port != 0:
            self._tcp_messenger = TCPMessenger(tcp_port,
                                               self._io_executor,
                                               loop=self._loop)
        if broadcast_port != 0:
            self._udp_messenger = UDPMessenger(broadcast_address,
                                               broadcast_port,
                                               self._io_executor, True,
                                               self._loop)

        self._listeners = {
            'tcp': self._tcp_messenger,
            'udp': self._udp_messenger,
        }

        for messenger in self._listeners.values():
            messenger.set_on_message(self._on_message)

        self._daemon = None
        self._message_queue = asyncio.Queue()

    def start(self):
        self._logger.info('Starting messengers...')
        for messenger in self._listeners.values():
            messenger.start()
        self._logger.info('Messengers started!')
        self._daemon = wrap_exc(asyncio. async (self._taking_messages_loop()),
                                self._logger)

    def stop(self):
        if self._daemon is not None:
            self._daemon.cancel()
            self._daemon = None
        self._tcp_messenger.stop()
        self._udp_messenger.stop()
        self._io_executor.shutdown(wait=False)

    def _serialize(self, message: Message):
        self._logger.debug('Serialized message: {}...'.format(
            message.__getstate__()))
        return serialize_message(message, ENCODING)

    @asyncio.coroutine
    def _on_message(self, message: Message, address):
        node_id = message.author_node_id
        if node_id in self._lost_nodes:
            self._lost_nodes.remove(node_id)
            self._logger.info(
                'Heard about lost node again! (node_id={}) (so lost nodes: {})'
                .format(node_id, self._lost_nodes))
        if node_id not in self.nodes:
            host = address[0]
            self._add_node(node_id, host)
            wrap_exc(
                asyncio. async (self.send_message(
                    node_id, Message(MessageType.PING, self.node_id))),
                self._logger)
        elif address[0] != self.nodes[node_id]:
            self._logger.error(
                'Unexpected state! My nodes: {}. {} != {} (node_id={})'.format(
                    self.nodes, address[0], self.nodes[node_id], node_id))

        if message.type == MessageType.TAKE_TOKEN:
            assert isinstance(message, TakeTokenMessage)
            for node_id, host in message.nodes.items():
                if node_id not in self.nodes:
                    self._add_node(node_id, host)
                else:
                    if host != self.nodes[node_id]:
                        self._logger.error(
                            'Unexpected state! My nodes: {}. Nodes in message: {}. {} != {} (node_id={})'
                            .format(self.nodes, message.nodes, host,
                                    self.nodes[node_id], node_id))

    def _add_node(self, node_id, host):
        for old_node_id, old_host in self.nodes.items():
            if old_host == host:
                self._logger.error(
                    'IP collision! (new node id={}, new host={}, old node id={}, old ip={})'
                    .format(node_id, host, old_node_id, old_host))
        self.nodes[node_id] = host
        self._logger.info('New node: {} at {}! (total number: {})'.format(
            node_id, host, len(self.nodes)))

        def format_node_id(node_id):
            return '{}{}'.format(node_id,
                                 '' if self.node_id != node_id else ' (me)')

        self._logger.info('Nodes order: {}'.format([
            (format_node_id(node_id), self.nodes[node_id])
            for node_id in sorted(self.nodes.keys())
        ]))

    @asyncio.coroutine
    def _taking_messages_loop(self):
        while True:
            takings = []
            for listener in self._listeners.values():
                takings.append(
                    wrap_exc(asyncio. async (listener.take_message()),
                             self._logger))
            with auto_cancellation(takings):
                done, pending = yield from asyncio.wait(
                    takings, return_when=concurrent.futures.FIRST_COMPLETED)
                for f in done:
                    message = yield from f
                    self._message_queue.put_nowait(message)
                    self._logger.debug(
                        'Message {} putted. Queue size: {}.'.format(
                            message.type, self._message_queue.qsize()))

    @asyncio.coroutine
    def take_message(self):
        message = yield from self._message_queue.get()
        self._logger.debug('Taking message: {}...'.format(message.type))
        return message

    @asyncio.coroutine
    def send_broadcast(self, message):
        message_bytes = self._serialize(message)
        yield from self._udp_messenger.send_message(message_bytes)

    @asyncio.coroutine
    def send_message(self, node_id, message):
        if node_id in self._lost_nodes:
            self._logger.error(
                'Sending message to node, that seems to be lost! (node id={}, message type={})'
                .format(node_id, message.type))
        self._logger.debug('Sending "{}" message to node {}...'.format(
            message.type, node_id))
        host = self.nodes[node_id]
        success = yield from self._tcp_messenger.send_message(
            host, self._serialize(message))
        if not success:
            self._lost_nodes.add(node_id)
            self._logger.warn(
                'Node seems to be lost! (node_id={})'.format(node_id))
            if self.node_id == node_id:
                self._logger.error('We can not connect with our-self?! ')
        return success

    def get_next_available_node_id(self):
        nodes = sorted(self.nodes.keys())
        next_index = (nodes.index(self.node_id) + 1)
        for candidate_id in nodes[next_index:] + nodes[:next_index]:
            if candidate_id not in self._lost_nodes:
                return candidate_id

    @property
    def node_id(self):
        return str(self._node_id or self._mac)

    @property
    def nodes(self):
        return self._nodes