Class AsyncJob

java.lang.Object
org.apache.juneau.console.views.AsyncJob

public final class AsyncJob extends Object
A single in-memory, one-shot async job — the streamed-progress + terminal-result unit of a future async write variant.
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
  • Method Details

    • id

      public String 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

      public Instant createdAt()
      Returns:
      The instant this job was created.
    • deadline

      public Instant deadline()
      Returns:
      The instant past which this job self-terminates on the next timeout check.
    • outputBytes

      public long outputBytes()
      Returns:
      The total number of streamed output bytes so far.
    • maxOutputBytes

      public long maxOutputBytes()
      Returns:
      The maximum streamed output bytes before self-termination.
    • maxSubscribers

      public int maxSubscribers()
      Returns:
      The maximum number of concurrent subscribers.
    • progress

      public boolean progress(String text)
      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 a failure carrying an app:output-limit-exceeded message 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 be null.
      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

      public boolean markEffectStarted()
      Marks that this job's outbound effect has begun, so a subsequent cancel() settles to cancelled-after-effect rather than cancelled (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() and enforceTimeout(Instant), so a producer that starts its effect only when this returns true 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

      public boolean effectStarted()
      Returns:
      Whether the outbound effect has begun (see markEffectStarted()).
    • complete

      public boolean complete(ActionResult value)
      Settles the job to a producer-supplied terminal result (success/failure/refusal/unknown).
      Parameters:
      value - The terminal result. Must not be null.
      Returns:
      true if this call settled the job; false if it was already terminal (first settle wins).
    • cancel

      public boolean cancel()
      Cancels the job, settling it to cancelled — or cancelled-after-effect when markEffectStarted() 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

      public boolean enforceTimeout(Instant now)
      Terminates the job if it is past its hard-timeout deadline, settling it to a non-optimistic unknown — 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 be null.
      Returns:
      true if this call timed the job out; false if it was already terminal or not yet past its deadline.
    • isTerminal

      public boolean isTerminal()
      Returns:
      Whether the job has settled to a terminal result.
    • result

      public ActionResult result()
      Returns:
      The terminal result, or null if the job is still running.
    • eventCount

      public int eventCount()
      Returns:
      The number of progress events buffered so far.
    • eventsFrom

      public List<String> eventsFrom(int fromIndex)
      Snapshots the progress events at or after fromIndex.

      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 fromIndex onward (empty if none/out of range).
    • awaitUpdate

      public AsyncJob.Update awaitUpdate(int fromIndex, Duration timeout)
      Blocks until there is a progress event at or after fromIndex, or the job is terminal, or timeout elapses — 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 be null.
      Returns:
      The new events (possibly empty) and the terminal result (null while still running).
    • acquireSubscriber

      public boolean acquireSubscriber()
      Attempts to register a subscriber, enforcing the maxSubscribers cap.
      Returns:
      true if a subscriber slot was acquired; false if the cap is already reached.
    • releaseSubscriber

      public void releaseSubscriber()
      Releases a subscriber slot previously acquired via acquireSubscriber().
    • subscriberCount

      public int subscriberCount()
      Returns:
      The current number of registered subscribers.
    • toString

      public String 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.

      Overrides:
      toString in class Object
      Returns:
      A value-free description.