# 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](waiting-for-follow-on-work.md)).

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](events-from-outside.md)), 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,
  with `waitUntil`. 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 and `jadpo dev` serve through the same handler, and
  `jadpo dev` only 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 dev` the 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](retries-and-giving-up.md)). A schedule's run is
  recorded failed with `deadline_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 binding `ctx.exports` gives its own `JadpoHandOn` entrypoint,
  naming each piece by its record. The fresh invocation reads the pieces
  back and runs them in its own `waitUntil`, within what is left of its
  own 30 seconds, counted from its start
  ([design notes](https://github.com/jadpo-dev/jadpo/blob/main/docs/language/notes/12-jobs-and-events.md), "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 back `pending`, its
  attempt not counted, as a route's `within:` gives one back. A batch
  run's lease is given back. A schedule's run is recorded
  `deadline_exceeded`, and its lease given back. A route waiting for its
  follow-on work stops waiting, as when `within:` passes. The server
  waits at most 10 seconds for this, then ends, and `jadpo dev` exits 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 dev` runs without
  them, and a signal ends it at once.
- **Backfills.** A backfill worked out in Jadpo
  ([Computed backfills](computed-backfills.md)) 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`.
