Waiting for follow-on work
An action does its own database work and leaves the rest, such as a second
call to a provider, to subscribers: code that runs when an event the action
emits is delivered (Subscribers).
Without anything more, the route answers once the action commits, and that
work carries on afterwards. When the client wants the finished result
rather than a 202, the route waits for the work with settle::
POST /orders { input: PlaceOrder output: OrderView status: SuccessStatus.created run: place_order(input, current_principal.user) settle: { answer: order_view(result.id) within: 5s }}
This route places the order, then waits up to 5 seconds for the work it
set off. It answers with order_view(result.id): 201 if the work finished,
202 if the 5 seconds ran out first. The body is an OrderView either way.
What the route waits for
After the action commits, the runtime delivers the events it emitted at once, while the request waits, and then the events their subscribers emit in turn. They run in the request's own invocation, each delivery as any is delivered (Delivery: at least once, safe to repeat), as the system limited to its event's tenant (Background work). The request's caller is given back before the answer runs.
The work has settled when every one of those deliveries has succeeded,
failed for good, or been parked as outcome_unknown. That covers a failure
that isn't retried, a defect (a bug in the code, such as an Int
overflow), a subscriber the build no longer has, and an event 16 deep in a
chain of events (event_chain_limit), so a loop of events settles too.
Some work can't be waited for:
- A delivery left to retry after a pause (
unavailable,rate_limited, or a store that couldn't be reached) hasn't settled, so the route stops waiting at once and answers 202. - So does a delivery the request couldn't claim.
- So does a batch run the action started. A batch is long work no route waits on, so a route whose action starts one answers 202 (Batches: work over many items).
Waiting stops at the first such piece. The rest is left for after the response, and a retry is left to the outbox's sweep, the runtime's regular pass over deliveries that are due (Retries and giving up).
The answer
Then the route runs answer:. It is a named query, given result (what
the action returned) or fields read through it, and nothing else; the
query reads who is asking through its policy. It runs as the request's
caller and under the query's policy, as a GET running it would. The route
answers with its result and the route's status:.
If within: passes first, the route runs the same query and answers
202. So the client gets the same output: either way, and knows from the
status whether the work finished. The work carries on in the background
either way, exactly as it would without settle:.
The query's failures are the route's too, beside the action's, in OpenAPI
and the record. A query that fails, through its missing: say, answers
with that failure, though the action committed.
Rules
- With
settle:,output:is the query's result type, not the action's. within:counts from when the request came in, asdeadline:does. It must be shorter than the deadline, leaving the answer time to run, or it isroute.settle_invalid. The answer isn't held to the deadline, because the action has already committed, so waiting never ends in a 504.- When
within:passes, the subscriber's action that is running is dropped and nothing it did is kept. Its delivery goes back topending, due at once, and that attempt isn't counted. Only its body is cut short, never its commit, and the delivery's own statements are never cut short, so a claim is never left in doubt. settle:goes only on a route that runs an action and answersSuccessStatus.okor.created, or it isroute.settle_invalid. AGETsets off nothing to wait for,.acceptedwould make both answers 202, and.no_contentsends no answer.- Only work this action set off is waited for, never other requests' work or published copies of events (Where events travel).
Repeats, OpenAPI and tests
- A repeat with the same
Idempotency-Key(Idempotent writes) runs the answer query again, rather than replaying a stored body, so it shows how far the work has got. The key keeps the query's arguments, each written by its parameter's codec. A repeat answers 202 until the work is known to have settled, and the route's status after that. So a process that stops between the commit and the answer never has a repeat say the work finished. - OpenAPI lists the route's status, "once the work it set off has
settled", and 202, each sending the query's result, and says a repeat
runs the answer again. The API record adds a
settle within <duration>line to the route. - A route test's request waits in the same way. A test host's timers never
go off, so
within:never passes in a test, and a follow-on left to retry gives 202.
Markdown for agents: /docs/waiting-for-follow-on-work.md.Built from Jadpo main at a707efb.