Beispiel #1
0
async def spawn_resource_daemons(
    *,
    settings: configuration.OperatorSettings,
    handlers: Sequence[handlers_.ResourceSpawningHandler],
    daemons: MutableMapping[containers.DaemonId, containers.Daemon],
    cause: causation.ResourceSpawningCause,
    memory: containers.ResourceMemory,
) -> Collection[float]:
    """
    Ensure that all daemons are spawned for this individual resource.

    This function can be called multiple times on multiple handling cycles
    (though usually should be called on the first-seen occasion), so it must
    be idempotent: not having duplicating side-effects on multiple calls.
    """
    if memory.live_fresh_body is None:  # for type-checking; "not None" is ensured in processing.
        raise RuntimeError(
            "A daemon is spawned with None as body. This is a bug. Please report."
        )
    for handler in handlers:
        daemon_id = containers.DaemonId(handler.id)
        if daemon_id not in daemons:
            stopper = primitives.DaemonStopper()
            daemon_cause = causation.DaemonCause(
                resource=cause.resource,
                logger=cause.logger,
                body=memory.live_fresh_body,
                memo=memory.user_data,
                patch=patches.Patch(
                ),  # not the same as the one-shot spawning patch!
                stopper=stopper,  # for checking (passed to kwargs)
            )
            daemon = containers.Daemon(
                stopper=stopper,  # for stopping (outside of causes)
                handler=handler,
                logger=logging_engine.LocalObjectLogger(body=cause.body,
                                                        settings=settings),
                task=asyncio.create_task(
                    _runner(
                        settings=settings,
                        handler=handler,
                        cause=daemon_cause,
                        memory=memory,
                    )),
            )
            daemons[daemon_id] = daemon
    return []
Beispiel #2
0
async def match_resource_daemons(
        *,
        settings: configuration.OperatorSettings,
        handlers: Sequence[handlers_.ResourceSpawningHandler],
        daemons: MutableMapping[containers.DaemonId, containers.Daemon],
) -> Collection[float]:
    """
    Re-match the running daemons with the filters, and stop those mismatching.

    Stopping can take few iterations, same as `stop_resource_daemons` would do.
    """
    matching_daemon_ids = {containers.DaemonId(handler.id) for handler in handlers}
    mismatching_daemons = {
        daemon_id: daemon
        for daemon_id, daemon in daemons.items()
        if daemon_id not in matching_daemon_ids
    }
    delays = await stop_resource_daemons(
        settings=settings,
        daemons=mismatching_daemons,
        reason=primitives.DaemonStoppingReason.FILTERS_MISMATCH,
    )
    return delays
Beispiel #3
0
async def _runner(
        *,
        settings: configuration.OperatorSettings,
        daemons: MutableMapping[containers.DaemonId, containers.Daemon],
        handler: handlers_.ResourceSpawningHandler,
        memory: containers.ResourceMemory,
        cause: causation.DaemonCause,
) -> None:
    """
    Guard a running daemon during its life cycle.

    Note: synchronous daemons are awaited to the exit and postpone cancellation.
    The runner will not exit until the thread exits. See `invoke` for details.
    """
    try:
        if isinstance(handler, handlers_.ResourceDaemonHandler):
            await _resource_daemon(settings=settings, handler=handler, cause=cause)
        elif isinstance(handler, handlers_.ResourceTimerHandler):
            await _resource_timer(settings=settings, handler=handler, cause=cause, memory=memory)
        else:
            raise RuntimeError("Cannot determine which task wrapper to use. This is a bug.")

    finally:

        # Prevent future re-spawns for those exited on their own, for no reason.
        # Only the filter-mismatching daemons can be re-spawned on future events.
        if cause.stopper.reason == primitives.DaemonStoppingReason.NONE:
            memory.forever_stopped.add(handler.id)

        # Save the memory by not remembering the exited daemons (they may be never re-spawned).
        del daemons[containers.DaemonId(handler.id)]

        # Whatever happened, make sure the sync threads of asyncio threaded executor are notified:
        # in a hope that they will exit maybe some time later to free the OS/asyncio resources.
        # A possible case: operator is exiting and cancelling all "hung" non-root tasks, etc.
        cause.stopper.set(reason=primitives.DaemonStoppingReason.DONE)