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
payload
of the appropriateflavour
in the background. The caller is not blocked, but cannot receive any return value or exceptions.Note
It is a fatal error if
payload
produces any value or exception.
- runtime.execute(payload, *args, flavour, **kwargs)
Run a
payload
of the appropriateflavour
until 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
asyncio
library. Payloads are gracefully cancelled.
trio
Coroutines implemented with the
trio
library. Payloads are gracefully cancelled.
threading
Subroutines implemented with the
threading
library. Payloads run as daemons and ungracefully terminated.