Futures

Let’s say that an operation, commonly an asynchronous physical operation, needs to be performed, such as activating a pump or a complex multi-step hardware sequence. These are operations where the primary purpose is coordinating state and keeping track of progress. KOS solves this using FutureWork. Unlike the standard Future object in Java, which is designed to perform a computation and return data, FutureWork is specialized for long-running physical operations. It provides a robust framework for managing the lifecycle of physical actions, offering granular control over task execution, progress monitoring, and user-initiated cancellation.

Lifecycle and Event System

FutureWork utilizes a cascading event system to handle its lifecycle. This system allows developers to attach callbacks to specific events in the future’s lifecycle, rather than managing complex state. The cascade of the lifecycle events are shown in the digram below.

The lifecycle events are as follows:

  • START: When a FutureWork object is created, it remains in a pending state until it is manually started, or it is automatically started when returned from a controller endpoint. This lifecycle event is triggered just before the object begins execution.
  • These are the four terminal states of a future:
    • SUCCESS: When the Future finishes successfully
    • FAIL: When the Future finishes with an error.
    • CANCEL: User-initiated cancellation of the Future.
    • ABORT: Software-initiated termination of the Future.
  • The terminal states lead into the following:
    • DONE: Triggered after a future is finished, by either a SUCESS or FAIL event.
    • TERMINATE: Triggered after a future is stopped, either after a CANCEL or ABORT event.
  • COMPLETE: High-level callback, triggered at there end of the work, regardless of the outcome.
  • FINISHED: Final callback after COMPLETE
  • INTERRUPT: Triggered specifically on cancel or abort if the underlying process was still running.

Client Integration

FutureWork includes built-in features to bridge the gap between backend logic and client interfaces. If a FutureWork is returned from an endpoint, it automatically broadcasts data about the work over a corresponding websocket. This broadcast includes core work data, such as progress and estimated time, which can be used to drive progress bars and other similar UI elements.

FutureWork eases client integration through two dedicated fields. The tracker is a client-supplied string that allows the client to correlate work data before it knows the assigned work ID. The clientData field allows developers to attach extra details to periodic broadcasts, such as individual pump statuses, enabling the UI to display rich, real-time information beyond a standard progress bar.

Furthermore, FutureWork features robust interrupt handling, allowing for safe, configurable stoppage of blocking physical processes, and includes automatic abandonment detection to ensure stalled tasks do not hang the system indefinitely.

Developers can also use append() or prepend() to attach custom logic to these lifecycle events. For instance, an ice agitation function can be prepended to the START event to run asynchronously, or a cleanup method can be appended to the FINISHED event to ensure resources are released.

Coordination Patterns

FutureWork provides patterns for grouping and coordination. These patterns ensure that complex graphs of work maintain predictable state propagation.

  • SequencedFuture: This pattern executes child futures one after another. Failure behavior is configurable, allowing the sequence to exit early upon the first failure or to continue regardless of the outcome, ensuring that logic flows reliably through multi-stage physical operations. For example, a water valve may be opened, followed by the activation of a flow sensor, and finally the activation of a pump, all in a specific order.
  • ParallelFuture: This pattern runs multiple futures concurrently. The framework manages the total duration of the group by tracking the longest child task and offers flexible termination policies, determining whether the failure of one sibling triggers the cancellation of the remaining tasks. For example, diagnostic checks on three different sensors can be run simultaneously, where the overall success of the task depends on all sensors responding successfully.

For common development scenarios, the framework provides simplified utilities. DelayFuture offers a non-blocking, interruptible way to introduce pauses into workflows. FailedFuture is useful for maintaining a consistent return type in methods that might fail validation logic before the work is instantiated, ensuring API contracts remain intact even when tasks cannot proceed. Additionally, the successAfterDelay(msTilSuccess) method can be used to mark a future as successful after a delay for futures that have a fixed time.