class TimerModel(SimModel, TimerService): TimerEvent = NamedTuple('TimerEvent', [('name', str), ('callback', Callable)]) def __init__(self, name): self._name = name self._ts = 0 self._outbox = ListEventStream() def process(self, draw, event: SimEvent): self._ts = event.timestamp if isinstance(event.payload, self.TimerEvent): if event.payload.name == self._name: event.payload.callback() def outbox(self): return self._outbox def error_status(self) -> Optional[str]: pass def get_current_time(self) -> float: return self._ts def schedule(self, delay: float, callback: Callable): # TODO: Some classes (like ViewChanger) sometimes try to schedule None events o_O if callback is None: return self._outbox.add( SimEvent(timestamp=self._ts + delay, payload=self.create_timer_event(callback))) def cancel(self, callback: Callable): self._outbox.remove_all( lambda ev: isinstance(ev.payload, self.TimerEvent) and ev.payload == self.create_timer_event(callback)) def create_timer_event(self, callback: Callable): return self.TimerEvent(name=self._name, callback=callback)
class NodeModel: def __init__(self, name: str, node_names: List[str], connections: PoolConnections): self._name = name self._node_names = node_names self._quorum = Quorums(len(node_names)) self._ts = 0 self._corrupted_name = None self._timer = TimerModel(name) self._connections = connections self._view_changer = create_view_changer(self) self._internal_outbox = ListEventStream() self.outbox = CompositeEventStream(self._internal_outbox, self._timer.outbox()) self._check_performance_timer = RepeatingTimer(self._timer, 30, self._check_performance) @property def name(self): return self._name @property def view_no(self): return self._view_changer.view_no @property def is_participating(self): return not self._view_changer.view_change_in_progress and not self.is_corrupted @property def primary_name(self): return self._primary_name(self._view_changer.view_no) @property def next_primary_name(self): view_no = self._view_changer.view_no if not self._view_changer.view_change_in_progress: view_no += 1 return self._primary_name(view_no) @property def current_primary_name(self): view_no = self._view_changer.view_no - 1 if not self._view_changer.view_change_in_progress: view_no += 1 return self._primary_name(view_no) @property def is_primary(self): return self.name == self.primary_name @property def is_primary_disconnected(self): return self._connections.are_connected(self._ts, (self.name, self.primary_name)) @property def is_corrupted(self): return self._corrupted_name == self.name @property def connected_nodes(self) -> List[int]: result = [] for name in self._node_names: if name == self.name: continue if self._connections.are_connected(self._ts, (self.name, name)): result.append(name) return result def restart(self): pass def outage(self, node: str): if node == self.primary_name: self._view_changer.on_primary_loss() self._flush_viewchanger_outbox() def corrupt(self, node: str): self._corrupted_name = node if self.name == node: self._check_performance_timer.stop() def process(self, draw, event: SimEvent): self._ts = event.timestamp self._timer.process(draw, event) if isinstance(event.payload, CatchupDoneEvent): if event.payload.node == self.name: self._view_changer.on_catchup_complete() if isinstance(event.payload, NetworkEvent): self.process_network(event.payload) self._flush_viewchanger_outbox() def process_network(self, message: NetworkEvent): if self.name == self._corrupted_name: return if message.dst != self.name: return self._view_changer.inBoxRouter.handleSync((message.payload, message.src)) def _flush_viewchanger_outbox(self): if self.name == self._corrupted_name: return for msg in self._view_changer.outBox: self._broadcast(msg) self._view_changer.outBox.clear() def _send(self, message, delay=1): self._internal_outbox.add(SimEvent(timestamp=self._ts + delay, payload=message)) self.outbox.sort() def _broadcast(self, payload): for name in self._node_names: if name == self.name: continue self._send(NetworkEvent(src=self.name, dst=name, payload=payload)) def _check_performance(self): if self.primary_name == self._corrupted_name: self._view_changer.on_master_degradation() self._flush_viewchanger_outbox() def _primary_name(self, view_no): return self._node_names[view_no % len(self._node_names)]