Class AsyncJob
The stream URL IS the capability
A job is addressed by its id, an unguessable 256-bit
SecureRandom token minted by the AsyncJobRegistry, matching
SynchronizerToken's bar. Because a browser EventSource
constructor cannot set an X-Csrf-Token header and cookies are unsound on a loopback port, the SSE GET that
streams a job's progress is Host-checked at the boundary and capability-gated by the unguessability of the
id — there is no session cookie and no CSRF-on-GET. Consequently the id is a secret:
toString() never renders it.
Server-side hard limits (a data-egress disclosure bound)
A job caps its own streamed output at maxOutputBytes bytes and terminates itself when that is exceeded,
and it terminates itself once past its hard-timeout deadline. Both are enforced here,
server-side — a client cannot be trusted to cancel — and both are disclosure bounds, not only heap
bounds: they cap how long, and how much, customer-adjacent triage content is streamed. The number of concurrent
subscribers is capped at maxSubscribers (a browser reload plus one straggler), enforced through
acquireSubscriber() / releaseSubscriber().
Progress buffer and resume
Progress events are kept, in order, for the job's whole life (bounded by the output cap) and are never trimmed, so
the position of an event never changes: events[i] is streamed with SSE id i+1. A subscriber that
reconnects with a Last-Event-ID resumes exactly after the last event it saw; a fresh subscriber is
live-only (see resumeCursor(String)).
Terminal outcomes reuse the frozen ActionResult contract
A job settles exactly once, to an ActionResult: a producer-supplied success/failure/refusal/unknown, a cancellation (distinguishing cancelled from cancelled-after-effect via
markEffectStarted()), an output-limit termination, or a timeout. The reserved async outcomes are read
off the frozen ActionResult.Outcome enum via its public outcome field — this contract is reused, never
re-versioned.
Notes:
- This class is thread-safe: a producer thread appends progress and settles the job while
subscriber threads drain it through
awaitUpdate(int, Duration).
See Also:
- Since:
- 10.0.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordThe result ofawaitUpdate(int, Duration): the newly-available progress events and the terminal result (null while the job is still running). -
Method Summary
Modifier and TypeMethodDescriptionbooleanAttempts to register a subscriber, enforcing themaxSubscriberscap.awaitUpdate(int fromIndex, Duration timeout) Blocks until there is a progress event at or afterfromIndex, or the job is terminal, ortimeoutelapses — then returns whichever is available.booleancancel()Cancels the job, settling it tocancelled— orcancelled-after-effectwhenmarkEffectStarted()was called first.booleancomplete(ActionResult value) Settles the job to a producer-supplied terminal result (success/failure/refusal/unknown).deadline()booleanbooleanenforceTimeout(Instant now) Terminates the job if it is past itshard-timeout deadline, settling it to a non-optimisticunknown— the effect may or may not have completed in the time cut off.inteventsFrom(int fromIndex) Snapshots the progress events at or afterfromIndex.id()The unguessable capability id — the whole of the job's access control (see the class javadoc).booleanbooleanMarks that this job's outbound effect has begun, so a subsequentcancel()settles tocancelled-after-effectrather thancancelled(the two are different outcomes and must not be collapsed).longintlongbooleanAppends a progress event.voidReleases a subscriber slot previously acquired viaacquireSubscriber().result()inttoString()Returns a description that does not include the capability id.
-
Method Details
-
id
The unguessable capability id — the whole of the job's access control (see the class javadoc).- Returns:
- The id. Never
null or blank.
-
createdAt
- Returns:
- The instant this job was created.
-
deadline
- Returns:
- The instant past which this job self-terminates on the next timeout check.
-
outputBytes
- Returns:
- The total number of streamed output bytes so far.
-
maxOutputBytes
- Returns:
- The maximum streamed output bytes before self-termination.
-
maxSubscribers
- Returns:
- The maximum number of concurrent subscribers.
-
progress
Appends a progress event.A no-op once the job is terminal. Enforces the server-side output cap: if this event would push the total over
maxOutputBytes, the job is instead terminated with afailurecarrying anapp:output-limit-exceededmessage and the event is dropped — the cap is a data-egress bound, so the excess content is never streamed.Each event is charged its UTF-8 payload size plus a fixed 1L-byte overhead toward the cap, so empty events cannot grow the buffer without bound.
outputBytes()still reports payload bytes only.- Parameters:
text- The progress text. Must not benull .- Returns:
true if the event was appended;false if the job was already terminal or the output cap was hit (which itself terminates the job).
-
markEffectStarted
Marks that this job's outbound effect has begun, so a subsequentcancel()settles tocancelled-after-effectrather thancancelled(the two are different outcomes and must not be collapsed). A no-op once terminal.The mark and the terminal check share one lock acquisition with
cancel()andenforceTimeout(Instant), so a producer that starts its effect only when this returnstrue can never start it on a job that has already been cancelled or timed out.- Returns:
true if the job is still running and is now marked;false if it was already terminal, in which case the producer must not start the effect.
-
effectStarted
- Returns:
- Whether the outbound effect has begun (see
markEffectStarted()).
-
complete
Settles the job to a producer-supplied terminal result (success/failure/refusal/unknown).- Parameters:
value- The terminal result. Must not benull .- Returns:
true if this call settled the job;false if it was already terminal (first settle wins).
-
cancel
Cancels the job, settling it tocancelled— orcancelled-after-effectwhenmarkEffectStarted()was called first. Enforced server-side; a client cannot be trusted to stop the work itself.- Returns:
true if this call settled the job;false if it was already terminal.
-
enforceTimeout
Terminates the job if it is past itshard-timeout deadline, settling it to a non-optimisticunknown— the effect may or may not have completed in the time cut off. This is the server-side timeout enforcement: it bounds both heap and, as a data-egress disclosure bound, how long triage keeps streaming customer-adjacent content.- Parameters:
now- The current instant. Must not benull .- Returns:
true if this call timed the job out;false if it was already terminal or not yet past its deadline.
-
isTerminal
- Returns:
- Whether the job has settled to a terminal result.
-
result
- Returns:
- The terminal result, or
null if the job is still running.
-
eventCount
- Returns:
- The number of progress events buffered so far.
-
eventsFrom
Snapshots the progress events at or afterfromIndex.Not the stream's resume path: this answers empty for an out-of-range index, whereas a past-the-end resume value is refused (see
resumeCursor(String)).- Parameters:
fromIndex- The zero-based index to start from.- Returns:
- A copy of the events from
fromIndexonward (empty if none/out of range).
-
awaitUpdate
Blocks until there is a progress event at or afterfromIndex, or the job is terminal, ortimeoutelapses — then returns whichever is available.A terminal job returns immediately with any remaining buffered events plus its terminal result, so a single subscriber loop drains a completed (or re-attached) job in one pass.
- Parameters:
fromIndex- The zero-based index the caller has already consumed up to.timeout- The maximum time to wait for a new event. Must not benull .- Returns:
- The new events (possibly empty) and the terminal result (
null while still running).
-
acquireSubscriber
Attempts to register a subscriber, enforcing themaxSubscriberscap.- Returns:
true if a subscriber slot was acquired;false if the cap is already reached.
-
releaseSubscriber
Releases a subscriber slot previously acquired viaacquireSubscriber(). -
subscriberCount
- Returns:
- The current number of registered subscribers.
-
toString
Returns a description that does not include the capability id.The id is the stream's whole access control (see the class javadoc); a bean-dumping logger or debug view that stringified it would write a live capability somewhere it can be read back and used to attach to the stream.
-