Results and cancellation
import gylo
app = gylo.Gylo()
@app.task(store_result=True)
async def analyse(document_id: int) -> dict:
return {"score": 0.97}
async def check_on(conn, job_id: int) -> None:
got = await gylo.outcome(conn, job_id)
if got is None:
... # no such job, or already pruned by retention
elif got.succeeded:
print(got.result)
elif got.finished:
print(got.state, got.errors[-1] if got.errors else None)
async def call_off(conn, job_ids: list[int]) -> None:
cancelled = await gylo.cancel(conn, *job_ids)
print(f"{cancelled} had not started and were cancelled")
Results are opt-in
store_result=True keeps the task's return value for retrieval with
gylo.outcome(conn, job_id). It is off by default deliberately: most jobs run
for their effects, and storing what nobody reads costs a write per job and
rows that exist only to be pruned. A task that opts in must return something
MessagePack can encode — anything else fails the job rather than storing
garbage.
outcome returns the job's state, the decoded result, and the full error
history (every failed attempt, timestamped). None means no such job — which
includes a job whose row retention already removed. The completed-retention
window is also the result-retrieval window (24 hours by default); results
that must outlive it belong in your own tables, written by the task itself —
ideally as a durable step.
There is no built-in blocking wait. Poll outcome if you must; but a caller
that needs the answer synchronously usually wanted a function call, not a
queue.
Cancellation is honest
gylo.cancel(conn, *ids) cancels jobs that have not started, and returns how
many that was. A job already running is left alone — and this is a stance,
not a gap. Interrupting Python mid-task from outside means killing the child
process, which takes every sibling job in it; queues that advertise "revoke"
either do that or quietly do nothing. gylo does the part that can be done
correctly and tells you exactly what it did.
For cooperative cancellation of long tasks, put a flag in your own data and have the task check it between units of work — with a timeout as the backstop for a task that stops checking.
Cancelling a workflow member behaves like any dead parent: descendants that depended on it are cancelled with it.