class ZyreNode(): def __init__(self, interface, netiface=None): self.interface = interface # Peers book self.book = {} self.topics = [] # Publisher self.pub_cache = {} self.publisher = Zsock.new_xpub(("tcp://*:*").encode()) # TimeServer self.timereply = Zsock.new_rep(("tcp://*:*").encode()) # Zyre self.zyre = Zyre(None) if netiface: self.zyre.set_interface(string_at(netiface)) self.interface.log("ZYRE Node forced netiface: ", string_at(netiface)) self.zyre.set_name(str(self.interface.hplayer.name()).encode()) self.zyre.set_header(b"TS-PORT", str(get_port(self.timereply)).encode()) self.zyre.set_header(b"PUB-PORT", str(get_port(self.publisher)).encode()) self.zyre.set_interval(PING_PEER) self.zyre.set_evasive_timeout(PING_PEER * 3) self.zyre.set_silent_timeout(PING_PEER * 5) self.zyre.set_expired_timeout(PING_PEER * 10) self.zyre.start() self.zyre.join(b"broadcast") self.zyre.join(b"sync") # Add self to book self.book[self.zyre.uuid()] = Peer( self, { 'uuid': self.zyre.uuid(), 'name': self.zyre.name().decode(), 'ip': '127.0.0.1', 'ts_port': get_port(self.timereply), 'pub_port': get_port(self.publisher) }) self.book[self.zyre.uuid()].subscribe(self.topics) # Start Poller self._actor_fn = zactor_fn( self.actor_fn ) # ctypes function reference must live as long as the actor. if netiface: netiface = create_string_buffer(str.encode(netiface)) self.actor = Zactor(self._actor_fn, netiface) self.done = False # ZYRE Zactor def actor_fn(self, pipe, netiface): # Internal internal_pipe = Zsock(pipe, False) # We don't own the pipe, so False. # Poller poller = Zpoller(self.zyre.socket(), internal_pipe, self.publisher, self.timereply, None) # RUN self.interface.log('Node started') internal_pipe.signal(0) while True: sock = poller.wait(500) if not sock: continue # # ZYRE receive # if sock == self.zyre.socket(): e = ZyreEvent(self.zyre) uuid = e.peer_uuid() # self.interface.log("ZYREmsg", uuid, e.peer_name().decode(), e.type().decode()) # ENTER: add to book for external contact (i.e. TimeSync) if e.type() == b"ENTER": newpeer = Peer(self, e) existing = None if uuid in self.book: # print ('UUID already exist: replacing') ## PROBLEM : Same name may appear with different uuid (not a real problem, only if crash and restart with new uuid in a short time..) self.book[uuid].stop() existing = uuid for p in self.book.values(): if p.name == newpeer.name: # print ('Name already exist: replacing') p.stop() existing = p.uuid if existing: del self.book[existing] self.book[uuid] = newpeer self.book[uuid].subscribe(self.topics) # EVASIVE elif e.type() == b"EVASIVE": # if uuid in self.book: # self.book[uuid].linker(2) pass # SILENT elif e.type() == b"SILENT": if uuid in self.book: self.book[uuid].linker(1) # EXIT elif e.type() == b"EXIT": if uuid in self.book: self.book[uuid].linker(0) self.book[uuid].stop() del self.book[uuid] # JOIN elif e.type() == b"JOIN": # self.interface.log("peer join a group..", e.peer_name(), e.group().decode()) # SYNC clocks if e.group() == b"sync": if self.peer(uuid): self.peer(uuid).sync() # LEAVE elif e.type() == b"LEAVE": # self.interface.log("peer left a group..") pass # SHOUT -> process event elif e.type() == b"SHOUT" or e.type() == b"WHISPER": # Parsing message data = json.loads(e.msg().popstr().decode()) data['from'] = uuid # add group if e.type() == b"SHOUT": data['group'] = e.group().decode() else: data['group'] = 'whisper' self.preProcessor1(data) # # PUBLISHER event # elif sock == self.publisher: msg = Zmsg.recv(self.publisher) if not msg: break topic = msg.popstr() # Somebody subscribed: push Last Value Cache ! if len(topic) > 0 and topic[0] == 1: topic = topic[1:] if topic in self.pub_cache: # self.interface.log('XPUB lvc send for', topic.decode()) msg = Zmsg.dup(self.pub_cache[topic]) Zmsg.send(msg, self.publisher) # else: # self.interface.log('XPUB lvc empty for', topic.decode()) # # TIMESERVER event # elif sock == self.timereply: msgin = Zmsg.recv(self.timereply) msg = Zmsg() msg.addstr(str(int(time.time() * PRECISION)).encode()) Zmsg.send(msg, self.timereply) # # INTERNAL commands # elif sock == internal_pipe: msg = Zmsg.recv(internal_pipe) if not msg or msg.popstr() == b"$TERM": # print('ZYRE Node TERM') break internal_pipe.__del__() self.interface.log(' - node stopped' ) # WEIRD: print helps the closing going smoothly.. self.done = True def stop(self): for peer in self.book.values(): peer.stop() self.actor.sock().send(b"ss", b"$TERM") retry = 0 while not self.done and retry < 10: sleep(0.1) retry += 1 # self.zyre.stop() # HANGS ! self.zyre.__del__() self.publisher.__del__() self.timereply.__del__() def peer(self, uuid): if uuid in self.book and self.book[uuid].active: return self.book[uuid] def peerByName(self, name): for peer in self.book.values(): if peer.active and peer.name == name: return peer # # PUB/SUB # def subscribe(self, topics): if not isinstance(topics, list): topics = [topics] self.topics = list(set(self.topics) | set(topics)) # merge lists and remove duplicates for peer in self.book.values(): peer.subscribe(self.topics) def publish(self, topic, args=None): topic = topic.encode() msg = Zmsg() msg.addstr(topic) msg.addstr(self.zyre.uuid()) msg.addstr(json.dumps(args).encode()) self.pub_cache[topic] = Zmsg.dup(msg) Zmsg.send(msg, self.publisher) # # ZYRE send messages # def makeMsg(self, event, args=None, delay_ms=0, at=0): data = {} data['event'] = event data['args'] = [] if args: if not isinstance(args, list): # self.interface.log('NOT al LIST', args) args = [args] data['args'] = args # at time if at > 0: data['at'] = at # add delay if delay_ms > 0: if not 'at' in data: data['at'] = 0 data['at'] += int(time.time() * PRECISION + delay_ms * PRECISION / 1000) return json.dumps(data).encode() def whisper(self, uuid, event, args=None, delay_ms=0, at=0): data = self.makeMsg(event, args, delay_ms, at) if uuid == self.zyre.uuid(): data = json.loads(data.decode()) data['from'] = 'self' data['group'] = 'whisper' self.preProcessor1(data) else: self.zyre.whispers(uuid, data) def shout(self, group, event, args=None, delay_ms=0, at=0): data = self.makeMsg(event, args, delay_ms, at) self.zyre.shouts(group.encode(), data) # if own group -> send to self too ! groups = zlist_strlist(self.zyre.own_groups()) if group in groups: data = json.loads(data.decode()) data['from'] = 'self' data['group'] = group self.preProcessor1(data) def broadcast(self, event, args=None, delay_ms=0, at=0): self.shout('broadcast', event, args, delay_ms, at) def join(self, group): self.zyre.join(group.encode()) def leave(self, group): self.zyre.leave(group.encode()) # # ZYRE messages processor # def preProcessor1(self, data): # if a programmed time is provided, correct it with peer CS # Set timer if 'at' in data: if self.peer(data['from']): data['at'] -= self.peer(data['from']).clockshift() delay = (data['at']) / PRECISION - time.time() if delay <= -10000: self.interface.log('WARNING event already passed by', delay, 's, its too late !! discarding... ') elif delay <= 0: self.interface.log('WARNING event already passed by', delay, 's, playing late... ') self.preProcessor2(data) elif delay > 3000: self.interface.log('WARNING event in', delay, 's, thats weird, playing now... ') self.preProcessor2(data) else: self.interface.log('programmed event in', delay, 's') t = Timer(delay, self.preProcessor2, args=[data]) t.start() self.interface.emit('planned', data) else: self.preProcessor2(data) def preProcessor2(self, data): self.interface.emit('event', *[data]) self.interface.emit(data['event'], *data['args'])