def monitor_heartbeats( coordinator: Coordinator, terminate_event: threading.Event ) -> None: """Monitors the heartbeat of participants. If a heartbeat expires the participant is removed from the :class:`~.Participants`. Note: This is meant to be run inside a thread and expects an :class:`~threading.Event`, to know when it should terminate. Args: coordinator (:class:`xain_fl.coordinator.coordinator.Coordinator`): The coordinator to monitor for heartbeats. terminate_event (:class:`~threading.Event`): A threading event to signal that this method should terminate. """ logger.info("Heartbeat monitor starting...") while not terminate_event.is_set(): participants_to_remove: List[str] = [] for participant in coordinator.participants.participants.values(): if participant.heartbeat_expires < time.time(): participants_to_remove.append(participant.participant_id) for participant_id in participants_to_remove: coordinator.remove_participant(participant_id) next_expiration: float = coordinator.participants.next_expiration() - time.time() logger.debug("Monitoring heartbeats", next_expiration=next_expiration) time.sleep(next_expiration)
def test_remove_participant(): coordinator = Coordinator(minimum_participants_in_round=1, fraction_of_participants=1.0) coordinator.on_message(coordinator_pb2.RendezvousRequest(), "participant1") assert coordinator.state == coordinator_pb2.State.ROUND coordinator.remove_participant("participant1") assert coordinator.participants.len() == 0 assert coordinator.state == coordinator_pb2.State.STANDBY coordinator.on_message(coordinator_pb2.RendezvousRequest(), "participant1") assert coordinator.state == coordinator_pb2.State.ROUND