def load_triggers(self): """ Queries the database to get the triggers we're supposed to be running, adds them to our runner, and then removes ones from it we no longer need. """ Trigger.assign_unassigned(self.id, self.capacity) ids = Trigger.ids_for_triggerer(self.id) self.runner.update_triggers(set(ids))
def handle_failed_triggers(self): """ Handles "failed" triggers - ones that errored or exited before they sent an event. Task Instances that depend on them need failing. """ while self.runner.failed_triggers: # Tell the model to fail this trigger's deps trigger_id = self.runner.failed_triggers.popleft() Trigger.submit_failure(trigger_id=trigger_id)
def handle_events(self): """ Handles outbound events from triggers - dispatching them into the Trigger model where they are then pushed into the relevant task instances. """ while self.runner.events: # Get the event and its trigger ID trigger_id, event = self.runner.events.popleft() # Tell the model to wake up its tasks Trigger.submit_event(trigger_id=trigger_id, event=event)
def _run_trigger_loop(self) -> None: """ The main-thread trigger loop. This runs synchronously and handles all database reads/writes. """ while not self.runner.stop: # Clean out unused triggers Trigger.clean_unused() # Load/delete triggers self.load_triggers() # Handle events self.handle_events() # Handle failed triggers self.handle_failed_triggers() # Idle sleep time.sleep(1)
def _run_trigger_loop(self) -> None: """ The main-thread trigger loop. This runs synchronously and handles all database reads/writes. """ while not self.runner.stop: # Clean out unused triggers Trigger.clean_unused() # Load/delete triggers self.load_triggers() # Handle events self.handle_events() # Handle failed triggers self.handle_failed_triggers() # Handle heartbeat self.heartbeat(only_if_necessary=True) # Collect stats self.emit_metrics() # Idle sleep time.sleep(1)
def update_triggers(self, requested_trigger_ids: Set[int]): """ Called from the main thread to request that we update what triggers we're running. Works out the differences - ones to add, and ones to remove - then adds them to the deques so the subthread can actually mutate the running trigger set. """ # Note that `triggers` could be mutated by the other thread during this # line's execution, but we consider that safe, since there's a strict # add -> remove -> never again lifecycle this function is already # handling. running_trigger_ids = set(self.triggers.keys()) known_trigger_ids = (running_trigger_ids.union( x[0] for x in self.events).union(self.to_cancel).union( x[0] for x in self.to_create).union( trigger[0] for trigger in self.failed_triggers)) # Work out the two difference sets new_trigger_ids = requested_trigger_ids - known_trigger_ids cancel_trigger_ids = running_trigger_ids - requested_trigger_ids # Bulk-fetch new trigger records new_triggers = Trigger.bulk_fetch(new_trigger_ids) # Add in new triggers for new_id in new_trigger_ids: # Check it didn't vanish in the meantime if new_id not in new_triggers: self.log.warning( "Trigger ID %s disappeared before we could start it", new_id) continue # Resolve trigger record into an actual class instance try: trigger_class = self.get_trigger_by_classpath( new_triggers[new_id].classpath) except BaseException as e: # Either the trigger code or the path to it is bad. Fail the trigger. self.failed_triggers.append((new_id, e)) continue self.to_create.append( (new_id, trigger_class(**new_triggers[new_id].kwargs))) # Enqueue orphaned triggers for cancellation for old_id in cancel_trigger_ids: self.to_cancel.append(old_id)