Simple timers and callbacks are generally straightforward using Java, but there are a number of common use cases in embedded systems that require more complex timer implementations. KOS provides various timer and callback related classes to handle many of these cases.
The Scheduler object in KOS provides access to various standard thread pools, including a scheduled thread pool. For simple timer needs, such as a callback after a delay, simply use the available Scheduler APIs to avoid having to manage timers.
For more complex timer needs, such as recurring callbacks with the ability to adjust the interval or use intervals with variances, use AdjustableTimer, which provides a host of generic timer functionality:
Single or recurring event using start()/stop()
to enable/disable the timer.
Ability to adjust the timer interval while the time is running using either an absolute new interval or an interval relative to the wait time in the current interval.
Support for interval variances using upper and lower percentage bounds.
Ability to force the current interval to trigger immediately, calling the callback in either a background thread or in the current thread to ensure synchronicity.
The features of AdjustableTimer make it a great choice for periodic tasks. For tasks that interact with external servers, variance support provides a simple way to avoid the tendency of multiple clients to align over time and cause server spikes. For components that uses the KOS configuration system to configure the interval, AdjustableTimer allows for configuration changes to be applied directly to the timer without any other overhead.
When performing work in response to an event, a common problem is event bursts. Consider a system that performs an expensive operation of recomputing cached data every time a switch is triggered. Performing this work in a callback from the switch can introduce two problems:
System latency: Performing the expensive computation in the event callback from the switch can cause the switch to appear unresponsive, as the callback thread is tied up for the duration of the computation.
Event storms: If the switch begins to fail and generates many spurious events, the system will be bogged down performing expensive computations over and over.
The primary purpose of TriggeredCallback is to decouple events from actions by running actions in a separate thread to avoid latency and using a short delay to absorb event storms. A TriggeredCallback is created with a callback and timer delay and processes can call trigger()
to start the timer. Once the timer is started, any additional calls to trigger()
will be ignored until the timer fires and performs the work. TriggeredCallback also handles the race condition where a call to trigger()
occurs while the callback is running.
TriggeredCallback has a number of other features that make it a powerful tool for decoupling events from work:
The timer delay can be changed on the fly, which allows it to work seamlessly with the KOS configuration system.
Applications can cause the timer to fire immediately using the flush()
or flushInline()
methods.
Applications can force the callback to fire immediately and cancel any pending timer by using the force()
or forceInline()
methods.
An existing pending timer can be aborted using the cancel()
method.
While TriggeredCallback is useful to trigger work in response to events, PushPullCallback handles the opposite case, where work should be performed when an event does not occur. This pattern is commonly referred to as a "dead man switch". Once created, the first call to push()
will enable the timer, and then any subsequent calls to push()
resets the timer. PushableCallback offers a few additional operations for timer management:
adjust() is used to change the timeout of the timer, whether a timer is scheduled or not. This allows the timer to be used seamlessly with the KOS configuration system.
flush() is used to force any pending timer to trigger immediately.
cancel() is used to disable the timer.