Background Tasks
Status: 🔴 DA REVISIONARE
What it does
Runs work outside the request/response cycle: a spool of queued jobs, an
executor that runs them, and a scheduler for interval and cron jobs. Tasks
are managed over HTTP under /_server/tasks.
When to use it
When an operation should not block the caller (send an email, crunch a report) or must run on a schedule (nightly cleanup, periodic sync). Arm the task backbone and drive it either from code or over HTTP.
Setup
Tasks are a mixin capability of AsgiServer, armed with the tasks kwarg:
from genro_asgi import AsgiServer, RoutedApplication
from genro_routes import route
class App(RoutedApplication):
@route()
def sum_sync(self, a: int = 0, b: int = 0) -> dict:
return {"result": a + b}
server = AsgiServer(primary=App(), tasks=True)
server.serve(host="127.0.0.1", port=8000)
tasks accepts:
True/False— arm or leave off.a dict
{"enabled": ..., "tick_seconds": ..., "mount": ...}for finer control.
Once armed, server.tasks (lazily provisioned) exposes the backbone:
.spool, .executor, .hub, .scheduler, .task_store, .worker_id.
Fire-and-forget
Queue a one-off job by creating a descriptor and handing it to the spool:
from genro_asgi.tasks import new_descriptor
d = new_descriptor(task_id, owner="alice", mount="", node_path="sum_sync")
server.tasks.spool.create(d, {"a": 2, "b": 3})
new_descriptor(...)builds the task descriptor: who owns it (owner), which mount it targets (mount), and which route to run (node_path).spool.create(descriptor, params)enqueues it with its call parameters.
The tasks symbols (TaskManager, new_descriptor, and the rest) import from
genro_asgi.tasks, not the top level.
Scheduling
Register a route as a scheduled task directly on the decorator:
@route(task="cleanup", task_every="1s")
def cleanup(self) -> dict:
return {"cleaned": True}
task="cleanup"— the task code.task_every="1s"— run on an interval. Usetask_cron=...instead for a cron expression.
The scheduler drives them; from code you can call scheduler.tick() to advance it
and scheduler.run_now(code) to trigger a scheduled task immediately.
Managing tasks over HTTP
The _server app exposes the task backbone under /_server/tasks/...:
schedule side:
list,create,enable,disable,run_now,logs.spool side:
spool_list,progress,cancel,result.
How to verify it
$ curl http://127.0.0.1:8000/_server/tasks/list
Queue a fire-and-forget job from code (as above), then poll its progress and result:
$ curl http://127.0.0.1:8000/_server/tasks/spool_list
$ curl "http://127.0.0.1:8000/_server/tasks/progress?..."
$ curl "http://127.0.0.1:8000/_server/tasks/result?..."
Gotchas
The task symbols come from
genro_asgi.tasks—from genro_asgi.tasks import new_descriptor, not from the package top level.server.tasksis lazy: reaching for it beforetasksis armed will not give you a live backbone. Arm it withtasks=True(or a config dict).Interval vs cron is
task_every=...ortask_cron=...on@route, not both.The MCP push stream (
GET /mcp) depends on the task backbone — it is405without it. See the MCP guide.