Where events travel
Events stay inside the Jadpo runtime. Each is stored in the outbox of the store the action writes, in the same transaction as the action, and the runtime delivers it:
- on a long-running server, from its own workers;
- on Cloudflare Workers, right after the commit (
waitUntil), with the once-a-minute tick sweeping up anything left.
So one place holds every log and trace, a request is never slowed by
delivery, and you don't need a queue service. A route that wants the
finished result rather than a 202 waits for the work its action set
off with settle:
(Waiting for follow-on work).
An outside queue is never how events reach your subscribers. A
connection can publish chosen public events outward, so other systems
can react to them. That is a bridge fed from the outbox after the
commit, at least once, with the trace context attached, to Cloudflare
Queues, SQS, Pub/Sub or a webhook. Reading a queue the other way,
through receiving, turns each message into one of the app's own events
in its own outbox (Events from outside), so the
runtime still delivers it to subscribers.
Running background work
The runtime drives one runner for all background work, so the interpreter and compiled code run it the same way. The runner calls an action with its arguments, as the system, in a boundary of its own, the unit that commits or rolls back as one, and runs what that action sets off after it. Every piece of deferred work has a record of its own in the store, a delivery or a batch run, so none is lost when an invocation stops short.
- After the response, never in it. A committed action's deliveries,
and the batch runs it started, are left for the runner, not run while
the request waits, except under
settle:. A deployable hands them to its host, which runs them once the response has been sent: on a native server, as a task on the worker thread that answered, with that worker's program, so no thread needs a program of its own; on Workers, withwaitUntil. What a host does with a request (answer it, and hand on what it left) and with a tick is one runtime type every host wraps. So a native server andjadpo devserve through the same handler, andjadpo devonly listens in to print its lines. Each piece gets a context of its own, under the host's clock as it starts and with no seed. A host with nothing to run it on, a test's or a tool's, drops it, and the tick picks up what a dropped piece left. - A budget for each invocation. An invocation is one run of the
host's code: after a response, a tick, or work handed on. Every
invocation that runs background work is given a time budget by its
host, at most the 15-minute limit of a run. On a native server and
under
jadpo devthe budget is that limit. A test's host may give less. On Workers it is 30 seconds after a response and for work handed on, and 15 minutes for the cron. A tenth of the budget, at most 5 seconds, is kept back for giving claims back: once no more than that is left, the runner takes no more work. - A run's limit within the budget. A subscriber's or a schedule's
action runs until the 15-minute limit, or until what is left of the
budget before the time kept back, whichever is sooner. Only the body
is timed, never its commit: the transaction is held open until the body
has its value, then committed outside the timing. So work that
committed is never given back as though it hadn't run, and a body that
ends past its limit keeps nothing. A delivery stopped this way counts
against its
retry:budget (Retries and giving up). A schedule's run is recorded failed withdeadline_exceeded. The purge schedule's run is held to the same limit. - Every lease the budget plus a minute. A delivery's claim, a schedule's lease and a batch run's lease last what is left of the claimer's budget plus a minute. So a Worker that is ended loses what it held at most 90 seconds after it began. On a native server, with the whole run limit as its budget, a lease lasts at most 16 minutes. A claim taken while a route waits for its follow-on work lasts the longest deadline a route has, 60 seconds, plus the minute.
- Work not reached waits in the store. A delivery an invocation
didn't reach stays
pending, and a batch run waits on its row. What an invocation after a response left goes on in a fresh invocation with a budget of its own, once the invocation ran something. An invocation that could run nothing leaves the work for the tick, rather than handing it on for ever. On a native server the fresh invocation is a task of its own. On Workers it is a request to the Worker itself, through the loopback bindingctx.exportsgives its ownJadpoHandOnentrypoint, naming each piece by its record. The fresh invocation reads the pieces back and runs them in its ownwaitUntil, within what is left of its own 30 seconds, counted from its start (design notes, "Work handed on, on Workers"). A hand-on the platform refuses, for example past its limit on how many invocations one request may chain, leaves the work in the store for the minute's cron. The tick finds what is due at its time, then runs each piece under the host's clock as that piece starts, so work that takes time never claims with a lease already past. - Stopping gives work back as it was. A native server asked to stop
(SIGTERM, or ctrl-c under
jadpo dev) accepts nothing more, starts no more ticks, and ends every budget at once. The action running stops, and nothing it did is kept. A delivery is given backpending, its attempt not counted, as a route'swithin:gives one back. A batch run's lease is given back. A schedule's run is recordeddeadline_exceeded, and its lease given back. A route waiting for its follow-on work stops waiting, as whenwithin:passes. The server waits at most 10 seconds for this, then ends, andjadpo devexits 0. A second signal ends the process at once, with 143 for SIGTERM or 130 for SIGINT. If the signals can't be watched,jadpo devruns without them, and a signal ends it at once. - Backfills. A backfill worked out in Jadpo (Computed backfills) runs its rows on the same runner, a batch at a time. Each value is kept and checked by its field's rules, as any value of the field's type is.
Not built yet: publishing events outward, and the error for signals that
can't be watched, cli.signal_handler_failed.
Markdown for agents: /docs/where-events-travel.md.Built from Jadpo main at a707efb.