Concurrent Execution
The cobald.daemon provides a dedicated concurrent execution environment.
This combines several execution mechanisms into a single, consistent runtime.
As a result, the daemon can consistently track the lifetime of tasks and react to failures.
The purpose of this is for components to execute concurrently, while ensuring each component is in a valid state. In this regard, the execution environment is similar to an init service such as systemd.
Registering Background Services
The primary entry point to the runtime is defining services:
the main threads of service instances are automatically started, tracked and handled by the cobald.daemon.
This allows services to update information, manage resources and react to changing conditions.
A service is defined by applying the service() decorator to a class.
This automatically schedules the run method of any instances for execution as a background task.
@service(flavour=threading)
class MyService(object):
# run method of any instances is executed in a thread once the daemon starts
def run():
...
Task Execution and Abortion
Any background task is adopted by the daemon runtime.
Adopted tasks are executed separately for each flavour;
this means that async code of the same flavour is never run in parallel.
However, tasks of non-async flavour, such as threading, and different flavours can be run in parallel.
Any adopted tasks are considered self-contained by the runtime. Most importantly, they have no parent that can receive return values or exceptions.
Warning
Any unhandled return values and exceptions are considered an error. The daemon automatically terminates in this case.
On termination, the daemon aborts all remaining background tasks. Whether this is graceful or not depends on the flavour of each task. In general, coroutines are gracefully terminated whereas subroutines are not.
Triggering Background Tasks
The execution environment is exposed as cobald.daemon.runtime,
an instance of ServiceRunner.
Via this entry point, new tasks may be launched after the daemon has started.
- runtime.adopt(payload, *args, flavour, **kwargs)
Run a
payloadof the appropriateflavourin the background. The caller is not blocked, but cannot receive any return value or exceptions.Note
It is a fatal error if
payloadproduces any value or exception.
- runtime.execute(payload, *args, flavour, **kwargs)
Run a
payloadof the appropriateflavouruntil completion. The caller is blocked during execution, and receives any return value or exceptions.
If *args or **kwargs are provided, the payload is run as payload(*args, **kwargs).
Available Flavours
Flavours are identified by the underlying module. The following types are currently supported:
asnycio
Coroutines implemented with the
asynciolibrary. Payloads are gracefully cancelled.
trio
Coroutines implemented with the
triolibrary. Payloads are gracefully cancelled.
threading
Subroutines implemented with the
threadinglibrary. Payloads run as daemons and ungracefully terminated.