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
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