class SimpleSubscribe(object): def __init__(self, debuglevel=0, debugname='Simple Subscribe'): self.debuglevel = debuglevel self.debugname = debugname self._error_string = '' self.on_error_string_changed = [] # ZeroMQ context = zmq.Context() context.linger = 0 self._context = context # pipe to signalize a shutdown self._shutdown = context.socket(zmq.PUSH) self._shutdown_uri = b'inproc://shutdown-%s' % str( uuid.uuid4()).encode() self._shutdown.bind(self._shutdown_uri) self._thread = None # socket worker tread self._tx_lock = threading.Lock() # lock for outgoing messages # Socket self.socket_uri = '' self._socket_topics = set() # more efficient to reuse protobuf messages self._socket_rx = Container() # callbacks self.on_socket_message_received = [] self.on_state_changed = [] # fsm self._fsm = Fysom({ 'initial': 'down', 'events': [ { 'name': 'start', 'src': 'down', 'dst': 'up' }, { 'name': 'any_msg_received', 'src': 'up', 'dst': 'up' }, { 'name': 'stop', 'src': 'up', 'dst': 'down' }, ], }) self._fsm.ondown = self._on_fsm_down self._fsm.onafterstart = self._on_fsm_start self._fsm.onup = self._on_fsm_up self._fsm.onafterany_msg_received = self._on_fsm_any_msg_received self._fsm.onafterstop = self._on_fsm_stop def _on_fsm_down(self, _): if self.debuglevel > 0: print('[%s]: state DOWN' % self.debugname) for cb in self.on_state_changed: cb('down') return True def _on_fsm_start(self, _): if self.debuglevel > 0: print('[%s]: event START' % self.debugname) self.start_socket() return True def _on_fsm_up(self, _): if self.debuglevel > 0: print('[%s]: state UP' % self.debugname) for cb in self.on_state_changed: cb('up') return True def _on_fsm_any_msg_received(self, _): if self.debuglevel > 0: print('[%s]: event ANY MSG RECEIVED' % self.debugname) return True def _on_fsm_stop(self, _): if self.debuglevel > 0: print('[%s]: event STOP' % self.debugname) self.stop_socket() return True @property def error_string(self): return self._error_string @error_string.setter def error_string(self, string): if self._error_string is string: return self._error_string = string for cb in self.on_error_string_changed: cb(string) def start(self): if self._fsm.isstate('down'): self._fsm.start() def stop(self): if self._fsm.isstate('up'): self._fsm.stop() def add_socket_topic(self, name): self._socket_topics.add(name) def remove_socket_topic(self, name): self._socket_topics.remove(name) def clear_socket_topics(self): self._socket_topics.clear() def _socket_worker(self, context, uri): poll = zmq.Poller() socket = context.socket(zmq.SUB) socket.setsockopt(zmq.LINGER, 0) socket.connect(uri) poll.register(socket, zmq.POLLIN) # subscribe is always connected to socket creation for topic in self._socket_topics: socket.setsockopt(zmq.SUBSCRIBE, topic.encode()) shutdown = context.socket(zmq.PULL) shutdown.connect(self._shutdown_uri) poll.register(shutdown, zmq.POLLIN) while True: s = dict(poll.poll()) if shutdown in s: shutdown.recv() return # shutdown signal if socket in s: self._socket_message_received(socket) def start_socket(self): self._thread = threading.Thread( target=self._socket_worker, args=( self._context, self.socket_uri, ), ) self._thread.start() def stop_socket(self): self._shutdown.send(b' ') # trigger socket thread shutdown self._thread = None # process all messages received on socket def _socket_message_received(self, socket): (identity, msg) = socket.recv_multipart() # identity is topic identity = identity.decode() try: self._socket_rx.ParseFromString(msg) except DecodeError as e: note = 'Protobuf Decode Error: ' + str(e) print(note) # TODO: decode error return if self.debuglevel > 0: print('[%s] received message' % self.debugname) if self.debuglevel > 1: print(self._socket_rx) rx = self._socket_rx # react to any incoming message if self._fsm.isstate('up'): self._fsm.any_msg_received() for cb in self.on_socket_message_received: cb(identity, rx)
class HalrcompSubscribe(object): def __init__(self, debuglevel=0, debugname='Halrcomp Subscribe'): self.debuglevel = debuglevel self.debugname = debugname self._error_string = '' self.on_error_string_changed = [] # ZeroMQ context = zmq.Context() context.linger = 0 self._context = context # pipe to signalize a shutdown self._shutdown = context.socket(zmq.PUSH) self._shutdown_uri = b'inproc://shutdown-%s' % str(uuid.uuid4()).encode() self._shutdown.bind(self._shutdown_uri) self._thread = None # socket worker tread self._tx_lock = threading.Lock() # lock for outgoing messages # Socket self.socket_uri = '' self._socket_topics = set() # more efficient to reuse protobuf messages self._socket_rx = Container() # Heartbeat self._heartbeat_lock = threading.Lock() self._heartbeat_interval = 2500 self._heartbeat_timer = None self._heartbeat_active = False self._heartbeat_liveness = 0 self._heartbeat_reset_liveness = 5 # callbacks self.on_socket_message_received = [] self.on_state_changed = [] # fsm self._fsm = Fysom( { 'initial': 'down', 'events': [ {'name': 'start', 'src': 'down', 'dst': 'trying'}, {'name': 'full_update_received', 'src': 'trying', 'dst': 'up'}, {'name': 'stop', 'src': 'trying', 'dst': 'down'}, {'name': 'heartbeat_timeout', 'src': 'up', 'dst': 'trying'}, {'name': 'heartbeat_tick', 'src': 'up', 'dst': 'up'}, {'name': 'any_msg_received', 'src': 'up', 'dst': 'up'}, {'name': 'stop', 'src': 'up', 'dst': 'down'}, ], } ) self._fsm.ondown = self._on_fsm_down self._fsm.onafterstart = self._on_fsm_start self._fsm.ontrying = self._on_fsm_trying self._fsm.onafterfull_update_received = self._on_fsm_full_update_received self._fsm.onafterstop = self._on_fsm_stop self._fsm.onup = self._on_fsm_up self._fsm.onafterheartbeat_timeout = self._on_fsm_heartbeat_timeout self._fsm.onafterheartbeat_tick = self._on_fsm_heartbeat_tick self._fsm.onafterany_msg_received = self._on_fsm_any_msg_received def _on_fsm_down(self, _): if self.debuglevel > 0: print('[%s]: state DOWN' % self.debugname) for cb in self.on_state_changed: cb('down') return True def _on_fsm_start(self, _): if self.debuglevel > 0: print('[%s]: event START' % self.debugname) self.start_socket() return True def _on_fsm_trying(self, _): if self.debuglevel > 0: print('[%s]: state TRYING' % self.debugname) for cb in self.on_state_changed: cb('trying') return True def _on_fsm_full_update_received(self, _): if self.debuglevel > 0: print('[%s]: event FULL UPDATE RECEIVED' % self.debugname) self.reset_heartbeat_liveness() self.start_heartbeat_timer() return True def _on_fsm_stop(self, _): if self.debuglevel > 0: print('[%s]: event STOP' % self.debugname) self.stop_heartbeat_timer() self.stop_socket() return True def _on_fsm_up(self, _): if self.debuglevel > 0: print('[%s]: state UP' % self.debugname) for cb in self.on_state_changed: cb('up') return True def _on_fsm_heartbeat_timeout(self, _): if self.debuglevel > 0: print('[%s]: event HEARTBEAT TIMEOUT' % self.debugname) self.stop_heartbeat_timer() self.stop_socket() self.start_socket() return True def _on_fsm_heartbeat_tick(self, _): if self.debuglevel > 0: print('[%s]: event HEARTBEAT TICK' % self.debugname) self.reset_heartbeat_timer() return True def _on_fsm_any_msg_received(self, _): if self.debuglevel > 0: print('[%s]: event ANY MSG RECEIVED' % self.debugname) self.reset_heartbeat_liveness() self.reset_heartbeat_timer() return True @property def error_string(self): return self._error_string @error_string.setter def error_string(self, string): if self._error_string is string: return self._error_string = string for cb in self.on_error_string_changed: cb(string) def start(self): if self._fsm.isstate('down'): self._fsm.start() def stop(self): if self._fsm.isstate('trying'): self._fsm.stop() elif self._fsm.isstate('up'): self._fsm.stop() def add_socket_topic(self, name): self._socket_topics.add(name) def remove_socket_topic(self, name): self._socket_topics.remove(name) def clear_socket_topics(self): self._socket_topics.clear() def _socket_worker(self, context, uri): poll = zmq.Poller() socket = context.socket(zmq.SUB) socket.setsockopt(zmq.LINGER, 0) socket.connect(uri) poll.register(socket, zmq.POLLIN) # subscribe is always connected to socket creation for topic in self._socket_topics: socket.setsockopt(zmq.SUBSCRIBE, topic.encode()) shutdown = context.socket(zmq.PULL) shutdown.connect(self._shutdown_uri) poll.register(shutdown, zmq.POLLIN) while True: s = dict(poll.poll()) if shutdown in s: shutdown.recv() return # shutdown signal if socket in s: self._socket_message_received(socket) def start_socket(self): self._thread = threading.Thread( target=self._socket_worker, args=(self._context, self.socket_uri) ) self._thread.start() def stop_socket(self): self._shutdown.send(b' ') # trigger socket thread shutdown self._thread = None def _heartbeat_timer_tick(self): with self._heartbeat_lock: self._heartbeat_timer = None # timer is dead on tick if self.debuglevel > 0: print('[%s] heartbeat timer tick' % self.debugname) self._heartbeat_liveness -= 1 if self._heartbeat_liveness == 0: if self._fsm.isstate('up'): self._fsm.heartbeat_timeout() return if self._fsm.isstate('up'): self._fsm.heartbeat_tick() def reset_heartbeat_liveness(self): self._heartbeat_liveness = self._heartbeat_reset_liveness def reset_heartbeat_timer(self): if not self._heartbeat_active: return self._heartbeat_lock.acquire() if self._heartbeat_timer: self._heartbeat_timer.cancel() self._heartbeat_timer = None if self._heartbeat_interval > 0: self._heartbeat_timer = threading.Timer( self._heartbeat_interval / 1000.0, self._heartbeat_timer_tick ) self._heartbeat_timer.start() self._heartbeat_lock.release() if self.debuglevel > 0: print('[%s] heartbeat timer reset' % self.debugname) def start_heartbeat_timer(self): self._heartbeat_active = True self.reset_heartbeat_timer() def stop_heartbeat_timer(self): self._heartbeat_active = False self._heartbeat_lock.acquire() if self._heartbeat_timer: self._heartbeat_timer.cancel() self._heartbeat_timer = None self._heartbeat_lock.release() # process all messages received on socket def _socket_message_received(self, socket): (identity, msg) = socket.recv_multipart() # identity is topic try: self._socket_rx.ParseFromString(msg) except DecodeError as e: note = 'Protobuf Decode Error: ' + str(e) print(note) # TODO: decode error return if self.debuglevel > 0: print('[%s] received message' % self.debugname) if self.debuglevel > 1: print(self._socket_rx) rx = self._socket_rx # react to any incoming message if self._fsm.isstate('up'): self._fsm.any_msg_received() # react to ping message if rx.type == pb.MT_PING: return # ping is uninteresting # react to halrcomp full update message elif rx.type == pb.MT_HALRCOMP_FULL_UPDATE: if rx.HasField('pparams'): interval = rx.pparams.keepalive_timer self._heartbeat_interval = interval if self._fsm.isstate('trying'): self._fsm.full_update_received() for cb in self.on_socket_message_received: cb(identity, rx)
class RpcClient(object): def __init__(self, debuglevel=0, debugname='RPC Client'): self.debuglevel = debuglevel self.debugname = debugname self._error_string = '' self.on_error_string_changed = [] # ZeroMQ context = zmq.Context() context.linger = 0 self._context = context # pipe to signalize a shutdown self._shutdown = context.socket(zmq.PUSH) self._shutdown_uri = b'inproc://shutdown-%s' % str( uuid.uuid4()).encode() self._shutdown.bind(self._shutdown_uri) # pipe for outgoing messages self._pipe = context.socket(zmq.PUSH) self._pipe_uri = b'inproc://pipe-%s' % str(uuid.uuid4()).encode() self._pipe.bind(self._pipe_uri) self._thread = None # socket worker tread self._tx_lock = threading.Lock() # lock for outgoing messages # Socket self.socket_uri = '' # more efficient to reuse protobuf messages self._socket_rx = Container() self._socket_tx = Container() # Heartbeat self._heartbeat_lock = threading.Lock() self._heartbeat_interval = 2500 self._heartbeat_timer = None self._heartbeat_active = False self._heartbeat_liveness = 0 self._heartbeat_reset_liveness = 5 # callbacks self.on_socket_message_received = [] self.on_state_changed = [] # fsm self._fsm = Fysom({ 'initial': 'down', 'events': [ { 'name': 'start', 'src': 'down', 'dst': 'trying' }, { 'name': 'any_msg_received', 'src': 'trying', 'dst': 'up' }, { 'name': 'heartbeat_timeout', 'src': 'trying', 'dst': 'trying' }, { 'name': 'heartbeat_tick', 'src': 'trying', 'dst': 'trying' }, { 'name': 'any_msg_sent', 'src': 'trying', 'dst': 'trying' }, { 'name': 'stop', 'src': 'trying', 'dst': 'down' }, { 'name': 'heartbeat_timeout', 'src': 'up', 'dst': 'trying' }, { 'name': 'heartbeat_tick', 'src': 'up', 'dst': 'up' }, { 'name': 'any_msg_received', 'src': 'up', 'dst': 'up' }, { 'name': 'any_msg_sent', 'src': 'up', 'dst': 'up' }, { 'name': 'stop', 'src': 'up', 'dst': 'down' }, ], }) self._fsm.ondown = self._on_fsm_down self._fsm.onafterstart = self._on_fsm_start self._fsm.ontrying = self._on_fsm_trying self._fsm.onafterany_msg_received = self._on_fsm_any_msg_received self._fsm.onafterheartbeat_timeout = self._on_fsm_heartbeat_timeout self._fsm.onafterheartbeat_tick = self._on_fsm_heartbeat_tick self._fsm.onafterany_msg_sent = self._on_fsm_any_msg_sent self._fsm.onafterstop = self._on_fsm_stop self._fsm.onup = self._on_fsm_up def _on_fsm_down(self, _): if self.debuglevel > 0: print('[%s]: state DOWN' % self.debugname) for cb in self.on_state_changed: cb('down') return True def _on_fsm_start(self, _): if self.debuglevel > 0: print('[%s]: event START' % self.debugname) self.start_socket() self.reset_heartbeat_liveness() self.send_ping() self.start_heartbeat_timer() return True def _on_fsm_trying(self, _): if self.debuglevel > 0: print('[%s]: state TRYING' % self.debugname) for cb in self.on_state_changed: cb('trying') return True def _on_fsm_any_msg_received(self, _): if self.debuglevel > 0: print('[%s]: event ANY MSG RECEIVED' % self.debugname) self.reset_heartbeat_liveness() self.reset_heartbeat_timer() return True def _on_fsm_heartbeat_timeout(self, _): if self.debuglevel > 0: print('[%s]: event HEARTBEAT TIMEOUT' % self.debugname) self.stop_socket() self.start_socket() self.reset_heartbeat_liveness() self.send_ping() return True def _on_fsm_heartbeat_tick(self, _): if self.debuglevel > 0: print('[%s]: event HEARTBEAT TICK' % self.debugname) self.send_ping() return True def _on_fsm_any_msg_sent(self, _): if self.debuglevel > 0: print('[%s]: event ANY MSG SENT' % self.debugname) self.reset_heartbeat_timer() return True def _on_fsm_stop(self, _): if self.debuglevel > 0: print('[%s]: event STOP' % self.debugname) self.stop_heartbeat_timer() self.stop_socket() return True def _on_fsm_up(self, _): if self.debuglevel > 0: print('[%s]: state UP' % self.debugname) for cb in self.on_state_changed: cb('up') return True @property def error_string(self): return self._error_string @error_string.setter def error_string(self, string): if self._error_string is string: return self._error_string = string for cb in self.on_error_string_changed: cb(string) def start(self): if self._fsm.isstate('down'): self._fsm.start() def stop(self): if self._fsm.isstate('trying'): self._fsm.stop() elif self._fsm.isstate('up'): self._fsm.stop() def _socket_worker(self, context, uri): poll = zmq.Poller() socket = context.socket(zmq.DEALER) socket.setsockopt(zmq.LINGER, 0) socket.connect(uri) poll.register(socket, zmq.POLLIN) shutdown = context.socket(zmq.PULL) shutdown.connect(self._shutdown_uri) poll.register(shutdown, zmq.POLLIN) pipe = context.socket(zmq.PULL) pipe.connect(self._pipe_uri) poll.register(pipe, zmq.POLLIN) while True: s = dict(poll.poll()) if shutdown in s: shutdown.recv() return # shutdown signal if pipe in s: socket.send(pipe.recv(), zmq.NOBLOCK) if socket in s: self._socket_message_received(socket) def start_socket(self): self._thread = threading.Thread( target=self._socket_worker, args=( self._context, self.socket_uri, ), ) self._thread.start() def stop_socket(self): self._shutdown.send(b' ') # trigger socket thread shutdown self._thread = None def _heartbeat_timer_tick(self): with self._heartbeat_lock: self._heartbeat_timer = None # timer is dead on tick if self.debuglevel > 0: print('[%s] heartbeat timer tick' % self.debugname) self._heartbeat_liveness -= 1 if self._heartbeat_liveness == 0: if self._fsm.isstate('up'): self._fsm.heartbeat_timeout() elif self._fsm.isstate('trying'): self._fsm.heartbeat_timeout() return if self._fsm.isstate('up'): self._fsm.heartbeat_tick() elif self._fsm.isstate('trying'): self._fsm.heartbeat_tick() def reset_heartbeat_liveness(self): self._heartbeat_liveness = self._heartbeat_reset_liveness def reset_heartbeat_timer(self): if not self._heartbeat_active: return self._heartbeat_lock.acquire() if self._heartbeat_timer: self._heartbeat_timer.cancel() self._heartbeat_timer = None if self._heartbeat_interval > 0: self._heartbeat_timer = threading.Timer( self._heartbeat_interval / 1000.0, self._heartbeat_timer_tick) self._heartbeat_timer.start() self._heartbeat_lock.release() if self.debuglevel > 0: print('[%s] heartbeat timer reset' % self.debugname) def start_heartbeat_timer(self): self._heartbeat_active = True self.reset_heartbeat_timer() def stop_heartbeat_timer(self): self._heartbeat_active = False self._heartbeat_lock.acquire() if self._heartbeat_timer: self._heartbeat_timer.cancel() self._heartbeat_timer = None self._heartbeat_lock.release() # process all messages received on socket def _socket_message_received(self, socket): msg = socket.recv() try: self._socket_rx.ParseFromString(msg) except DecodeError as e: note = 'Protobuf Decode Error: ' + str(e) print(note) # TODO: decode error return if self.debuglevel > 0: print('[%s] received message' % self.debugname) if self.debuglevel > 1: print(self._socket_rx) rx = self._socket_rx # react to any incoming message if self._fsm.isstate('trying'): self._fsm.any_msg_received() elif self._fsm.isstate('up'): self._fsm.any_msg_received() # react to ping acknowledge message if rx.type == pb.MT_PING_ACKNOWLEDGE: return # ping acknowledge is uninteresting for cb in self.on_socket_message_received: cb(rx) def send_socket_message(self, msg_type, tx): with self._tx_lock: tx.type = msg_type if self.debuglevel > 0: print('[%s] sending message: %s' % (self.debugname, msg_type)) if self.debuglevel > 1: print(str(tx)) self._pipe.send(tx.SerializeToString()) tx.Clear() if self._fsm.isstate('up'): self._fsm.any_msg_sent() elif self._fsm.isstate('trying'): self._fsm.any_msg_sent() def send_ping(self): tx = self._socket_tx self.send_socket_message(pb.MT_PING, tx)
class Subscribe(object): def __init__(self, debuglevel=0, debugname='Subscribe'): self.debuglevel = debuglevel self.debugname = debugname self._error_string = '' self.on_error_string_changed = [] # ZeroMQ context = zmq.Context() context.linger = 0 self._context = context # pipe to signalize a shutdown self._shutdown = context.socket(zmq.PUSH) self._shutdown_uri = b'inproc://shutdown-%s' % str( uuid.uuid4()).encode() self._shutdown.bind(self._shutdown_uri) self._thread = None # socket worker tread self._tx_lock = threading.Lock() # lock for outgoing messages # Socket self.socket_uri = '' self._socket_topics = set() # more efficient to reuse protobuf messages self._socket_rx = Container() # Heartbeat self._heartbeat_lock = threading.Lock() self._heartbeat_interval = 2500 self._heartbeat_timer = None self._heartbeat_active = False self._heartbeat_liveness = 0 self._heartbeat_reset_liveness = 5 # callbacks self.on_socket_message_received = [] self.on_state_changed = [] # fsm self._fsm = Fysom({ 'initial': 'down', 'events': [ { 'name': 'start', 'src': 'down', 'dst': 'trying' }, { 'name': 'full_update_received', 'src': 'trying', 'dst': 'up' }, { 'name': 'stop', 'src': 'trying', 'dst': 'down' }, { 'name': 'heartbeat_timeout', 'src': 'up', 'dst': 'trying' }, { 'name': 'heartbeat_tick', 'src': 'up', 'dst': 'up' }, { 'name': 'any_msg_received', 'src': 'up', 'dst': 'up' }, { 'name': 'stop', 'src': 'up', 'dst': 'down' }, ], }) self._fsm.ondown = self._on_fsm_down self._fsm.onafterstart = self._on_fsm_start self._fsm.ontrying = self._on_fsm_trying self._fsm.onafterfull_update_received = self._on_fsm_full_update_received self._fsm.onafterstop = self._on_fsm_stop self._fsm.onup = self._on_fsm_up self._fsm.onafterheartbeat_timeout = self._on_fsm_heartbeat_timeout self._fsm.onafterheartbeat_tick = self._on_fsm_heartbeat_tick self._fsm.onafterany_msg_received = self._on_fsm_any_msg_received def _on_fsm_down(self, _): if self.debuglevel > 0: print('[%s]: state DOWN' % self.debugname) for cb in self.on_state_changed: cb('down') return True def _on_fsm_start(self, _): if self.debuglevel > 0: print('[%s]: event START' % self.debugname) self.start_socket() return True def _on_fsm_trying(self, _): if self.debuglevel > 0: print('[%s]: state TRYING' % self.debugname) for cb in self.on_state_changed: cb('trying') return True def _on_fsm_full_update_received(self, _): if self.debuglevel > 0: print('[%s]: event FULL UPDATE RECEIVED' % self.debugname) self.reset_heartbeat_liveness() self.start_heartbeat_timer() return True def _on_fsm_stop(self, _): if self.debuglevel > 0: print('[%s]: event STOP' % self.debugname) self.stop_heartbeat_timer() self.stop_socket() return True def _on_fsm_up(self, _): if self.debuglevel > 0: print('[%s]: state UP' % self.debugname) for cb in self.on_state_changed: cb('up') return True def _on_fsm_heartbeat_timeout(self, _): if self.debuglevel > 0: print('[%s]: event HEARTBEAT TIMEOUT' % self.debugname) self.stop_heartbeat_timer() self.stop_socket() self.start_socket() return True def _on_fsm_heartbeat_tick(self, _): if self.debuglevel > 0: print('[%s]: event HEARTBEAT TICK' % self.debugname) self.reset_heartbeat_timer() return True def _on_fsm_any_msg_received(self, _): if self.debuglevel > 0: print('[%s]: event ANY MSG RECEIVED' % self.debugname) self.reset_heartbeat_liveness() self.reset_heartbeat_timer() return True @property def error_string(self): return self._error_string @error_string.setter def error_string(self, string): if self._error_string is string: return self._error_string = string for cb in self.on_error_string_changed: cb(string) def start(self): if self._fsm.isstate('down'): self._fsm.start() def stop(self): if self._fsm.isstate('trying'): self._fsm.stop() elif self._fsm.isstate('up'): self._fsm.stop() def add_socket_topic(self, name): self._socket_topics.add(name) def remove_socket_topic(self, name): self._socket_topics.remove(name) def clear_socket_topics(self): self._socket_topics.clear() def _socket_worker(self, context, uri): poll = zmq.Poller() socket = context.socket(zmq.SUB) socket.setsockopt(zmq.LINGER, 0) socket.connect(uri) poll.register(socket, zmq.POLLIN) # subscribe is always connected to socket creation for topic in self._socket_topics: socket.setsockopt(zmq.SUBSCRIBE, topic.encode()) shutdown = context.socket(zmq.PULL) shutdown.connect(self._shutdown_uri) poll.register(shutdown, zmq.POLLIN) while True: s = dict(poll.poll()) if shutdown in s: shutdown.recv() return # shutdown signal if socket in s: self._socket_message_received(socket) def start_socket(self): self._thread = threading.Thread( target=self._socket_worker, args=( self._context, self.socket_uri, ), ) self._thread.start() def stop_socket(self): self._shutdown.send(b' ') # trigger socket thread shutdown self._thread = None def _heartbeat_timer_tick(self): with self._heartbeat_lock: self._heartbeat_timer = None # timer is dead on tick if self.debuglevel > 0: print('[%s] heartbeat timer tick' % self.debugname) self._heartbeat_liveness -= 1 if self._heartbeat_liveness == 0: if self._fsm.isstate('up'): self._fsm.heartbeat_timeout() return if self._fsm.isstate('up'): self._fsm.heartbeat_tick() def reset_heartbeat_liveness(self): self._heartbeat_liveness = self._heartbeat_reset_liveness def reset_heartbeat_timer(self): if not self._heartbeat_active: return self._heartbeat_lock.acquire() if self._heartbeat_timer: self._heartbeat_timer.cancel() self._heartbeat_timer = None if self._heartbeat_interval > 0: self._heartbeat_timer = threading.Timer( self._heartbeat_interval / 1000.0, self._heartbeat_timer_tick) self._heartbeat_timer.start() self._heartbeat_lock.release() if self.debuglevel > 0: print('[%s] heartbeat timer reset' % self.debugname) def start_heartbeat_timer(self): self._heartbeat_active = True self.reset_heartbeat_timer() def stop_heartbeat_timer(self): self._heartbeat_active = False self._heartbeat_lock.acquire() if self._heartbeat_timer: self._heartbeat_timer.cancel() self._heartbeat_timer = None self._heartbeat_lock.release() # process all messages received on socket def _socket_message_received(self, socket): (identity, msg) = socket.recv_multipart() # identity is topic identity = identity.decode() try: self._socket_rx.ParseFromString(msg) except DecodeError as e: note = 'Protobuf Decode Error: ' + str(e) print(note) # TODO: decode error return if self.debuglevel > 0: print('[%s] received message' % self.debugname) if self.debuglevel > 1: print(self._socket_rx) rx = self._socket_rx # react to any incoming message if self._fsm.isstate('up'): self._fsm.any_msg_received() # react to ping message if rx.type == pb.MT_PING: return # ping is uninteresting # react to full update message elif rx.type == pb.MT_FULL_UPDATE: if rx.HasField('pparams'): interval = rx.pparams.keepalive_timer self._heartbeat_interval = interval if self._fsm.isstate('trying'): self._fsm.full_update_received() for cb in self.on_socket_message_received: cb(identity, rx)
class RpcClient(object): def __init__(self, debuglevel=0, debugname='RPC Client'): self.debuglevel = debuglevel self.debugname = debugname self._error_string = '' self.on_error_string_changed = [] # ZeroMQ context = zmq.Context() context.linger = 0 self._context = context # pipe to signalize a shutdown self._shutdown = context.socket(zmq.PUSH) self._shutdown_uri = b'inproc://shutdown-%s' % str(uuid.uuid4()).encode() self._shutdown.bind(self._shutdown_uri) # pipe for outgoing messages self._pipe = context.socket(zmq.PUSH) self._pipe_uri = b'inproc://pipe-%s' % str(uuid.uuid4()).encode() self._pipe.bind(self._pipe_uri) self._thread = None # socket worker tread self._tx_lock = threading.Lock() # lock for outgoing messages # Socket self.socket_uri = '' # more efficient to reuse protobuf messages self._socket_rx = Container() self._socket_tx = Container() # Heartbeat self._heartbeat_lock = threading.Lock() self._heartbeat_interval = 2500 self._heartbeat_timer = None self._heartbeat_active = False self._heartbeat_liveness = 0 self._heartbeat_reset_liveness = 5 # callbacks self.on_socket_message_received = [] self.on_state_changed = [] # fsm self._fsm = Fysom( { 'initial': 'down', 'events': [ {'name': 'start', 'src': 'down', 'dst': 'trying'}, {'name': 'any_msg_received', 'src': 'trying', 'dst': 'up'}, {'name': 'heartbeat_timeout', 'src': 'trying', 'dst': 'trying'}, {'name': 'heartbeat_tick', 'src': 'trying', 'dst': 'trying'}, {'name': 'any_msg_sent', 'src': 'trying', 'dst': 'trying'}, {'name': 'stop', 'src': 'trying', 'dst': 'down'}, {'name': 'heartbeat_timeout', 'src': 'up', 'dst': 'trying'}, {'name': 'heartbeat_tick', 'src': 'up', 'dst': 'up'}, {'name': 'any_msg_received', 'src': 'up', 'dst': 'up'}, {'name': 'any_msg_sent', 'src': 'up', 'dst': 'up'}, {'name': 'stop', 'src': 'up', 'dst': 'down'}, ], } ) self._fsm.ondown = self._on_fsm_down self._fsm.onafterstart = self._on_fsm_start self._fsm.ontrying = self._on_fsm_trying self._fsm.onafterany_msg_received = self._on_fsm_any_msg_received self._fsm.onafterheartbeat_timeout = self._on_fsm_heartbeat_timeout self._fsm.onafterheartbeat_tick = self._on_fsm_heartbeat_tick self._fsm.onafterany_msg_sent = self._on_fsm_any_msg_sent self._fsm.onafterstop = self._on_fsm_stop self._fsm.onup = self._on_fsm_up def _on_fsm_down(self, _): if self.debuglevel > 0: print('[%s]: state DOWN' % self.debugname) for cb in self.on_state_changed: cb('down') return True def _on_fsm_start(self, _): if self.debuglevel > 0: print('[%s]: event START' % self.debugname) self.start_socket() self.reset_heartbeat_liveness() self.send_ping() self.start_heartbeat_timer() return True def _on_fsm_trying(self, _): if self.debuglevel > 0: print('[%s]: state TRYING' % self.debugname) for cb in self.on_state_changed: cb('trying') return True def _on_fsm_any_msg_received(self, _): if self.debuglevel > 0: print('[%s]: event ANY MSG RECEIVED' % self.debugname) self.reset_heartbeat_liveness() self.reset_heartbeat_timer() return True def _on_fsm_heartbeat_timeout(self, _): if self.debuglevel > 0: print('[%s]: event HEARTBEAT TIMEOUT' % self.debugname) self.stop_socket() self.start_socket() self.reset_heartbeat_liveness() self.send_ping() return True def _on_fsm_heartbeat_tick(self, _): if self.debuglevel > 0: print('[%s]: event HEARTBEAT TICK' % self.debugname) self.send_ping() return True def _on_fsm_any_msg_sent(self, _): if self.debuglevel > 0: print('[%s]: event ANY MSG SENT' % self.debugname) self.reset_heartbeat_timer() return True def _on_fsm_stop(self, _): if self.debuglevel > 0: print('[%s]: event STOP' % self.debugname) self.stop_heartbeat_timer() self.stop_socket() return True def _on_fsm_up(self, _): if self.debuglevel > 0: print('[%s]: state UP' % self.debugname) for cb in self.on_state_changed: cb('up') return True @property def error_string(self): return self._error_string @error_string.setter def error_string(self, string): if self._error_string is string: return self._error_string = string for cb in self.on_error_string_changed: cb(string) def start(self): if self._fsm.isstate('down'): self._fsm.start() def stop(self): if self._fsm.isstate('trying'): self._fsm.stop() elif self._fsm.isstate('up'): self._fsm.stop() def _socket_worker(self, context, uri): poll = zmq.Poller() socket = context.socket(zmq.DEALER) socket.setsockopt(zmq.LINGER, 0) socket.connect(uri) poll.register(socket, zmq.POLLIN) shutdown = context.socket(zmq.PULL) shutdown.connect(self._shutdown_uri) poll.register(shutdown, zmq.POLLIN) pipe = context.socket(zmq.PULL) pipe.connect(self._pipe_uri) poll.register(pipe, zmq.POLLIN) while True: s = dict(poll.poll()) if shutdown in s: shutdown.recv() return # shutdown signal if pipe in s: socket.send(pipe.recv(), zmq.NOBLOCK) if socket in s: self._socket_message_received(socket) def start_socket(self): self._thread = threading.Thread( target=self._socket_worker, args=(self._context, self.socket_uri) ) self._thread.start() def stop_socket(self): self._shutdown.send(b' ') # trigger socket thread shutdown self._thread = None def _heartbeat_timer_tick(self): with self._heartbeat_lock: self._heartbeat_timer = None # timer is dead on tick if self.debuglevel > 0: print('[%s] heartbeat timer tick' % self.debugname) self._heartbeat_liveness -= 1 if self._heartbeat_liveness == 0: if self._fsm.isstate('up'): self._fsm.heartbeat_timeout() elif self._fsm.isstate('trying'): self._fsm.heartbeat_timeout() return if self._fsm.isstate('up'): self._fsm.heartbeat_tick() elif self._fsm.isstate('trying'): self._fsm.heartbeat_tick() def reset_heartbeat_liveness(self): self._heartbeat_liveness = self._heartbeat_reset_liveness def reset_heartbeat_timer(self): if not self._heartbeat_active: return self._heartbeat_lock.acquire() if self._heartbeat_timer: self._heartbeat_timer.cancel() self._heartbeat_timer = None if self._heartbeat_interval > 0: self._heartbeat_timer = threading.Timer( self._heartbeat_interval / 1000.0, self._heartbeat_timer_tick ) self._heartbeat_timer.start() self._heartbeat_lock.release() if self.debuglevel > 0: print('[%s] heartbeat timer reset' % self.debugname) def start_heartbeat_timer(self): self._heartbeat_active = True self.reset_heartbeat_timer() def stop_heartbeat_timer(self): self._heartbeat_active = False self._heartbeat_lock.acquire() if self._heartbeat_timer: self._heartbeat_timer.cancel() self._heartbeat_timer = None self._heartbeat_lock.release() # process all messages received on socket def _socket_message_received(self, socket): msg = socket.recv() try: self._socket_rx.ParseFromString(msg) except DecodeError as e: note = 'Protobuf Decode Error: ' + str(e) print(note) # TODO: decode error return if self.debuglevel > 0: print('[%s] received message' % self.debugname) if self.debuglevel > 1: print(self._socket_rx) rx = self._socket_rx # react to any incoming message if self._fsm.isstate('trying'): self._fsm.any_msg_received() elif self._fsm.isstate('up'): self._fsm.any_msg_received() # react to ping acknowledge message if rx.type == pb.MT_PING_ACKNOWLEDGE: return # ping acknowledge is uninteresting for cb in self.on_socket_message_received: cb(rx) def send_socket_message(self, msg_type, tx): with self._tx_lock: tx.type = msg_type if self.debuglevel > 0: print('[%s] sending message: %s' % (self.debugname, msg_type)) if self.debuglevel > 1: print(str(tx)) self._pipe.send(tx.SerializeToString()) tx.Clear() if self._fsm.isstate('up'): self._fsm.any_msg_sent() elif self._fsm.isstate('trying'): self._fsm.any_msg_sent() def send_ping(self): tx = self._socket_tx self.send_socket_message(pb.MT_PING, tx)