Schedules define stateful, possibly effectful, recurring schedules of events, and compose in a variety of ways. Combinators allow us to take schedules and combine them together to get other schedules.
To demonstrate the functionality of different schedules, we will use the following helper function that logs the delay between executions.
Composition
Schedules can be composed in different ways:
Mode
Description
Union
Combines two schedules and recurs if either schedule wants to continue, using the shorter delay.
Intersection
Combines two schedules and recurs only if both schedules want to continue, using the longer delay.
Sequencing
Combines two schedules by running the first one fully, then switching to the second.
Union
Combines two schedules using union. The schedule recurs as long as one of the schedules wants to, using the minimum delay between recurrences.
Example (Union of Exponential and Spaced Schedules)
When we use the combined schedule with Effect.repeat, we observe that the effect is executed repeatedly based on the minimum delay between the two schedules. In this case, the delay alternates between the exponential schedule (increasing delay) and the spaced schedule (constant delay).
Intersection
Combines two schedules using intersection. The schedule recurs only if both schedules want to continue, using the maximum delay between them.
Example (Intersection of Exponential and Recurs Schedules)
When we use the combined schedule with Effect.repeat, we observe that the effect is executed repeatedly only if both schedules want it to recur. The delay between recurrences is determined by the maximum delay between the two schedules. In this case, the delay follows the progression of the exponential schedule until the maximum number of recurrences specified by the recursive schedule is reached.
Sequencing
Combines two schedules in sequence. First, it follows the policy of the first schedule, then switches to the second schedule once the first completes.
Example (Sequencing Recurs and Spaced Schedules)
When we use the combined schedule with Effect.repeat, we observe that the effect follows the policy of the first schedule (recurs) until it completes the specified number of recurrences. After that, it switches to the policy of the second schedule (spaced) and continues repeating the effect with the fixed delay between recurrences.
Jittering
A jittered is a combinator that takes one schedule and returns another schedule of the same type except for the delay which is applied randomly
When a resource is out of service due to overload or contention, retrying and backing off doesn’t help us. If all failed API calls are backed off to the same point of time, they cause another overload or contention. Jitter adds some amount of randomness to the delay of the schedule. This helps us to avoid ending up accidentally synchronizing and taking the service down by accident.
Research shows that Schedule.jittered(0.0, 1.0) is very suitable for retrying.
Example (Jittered Exponential Schedule)
In this example, we use the jittered combinator to apply jitter to an exponential schedule. The exponential schedule increases the delay between each repetition exponentially. By adding jitter to the schedule, the delays become randomly adjusted within a certain range.
Filtering
Schedules can be filtered using Schedule.whileInput or Schedule.whileOutput to control repetition based on input or output conditions.
Example (Filtering Output)
In this example, we create a schedule using Schedule.recurs(5) to repeat a certain action up to 5 times. However, we apply the whileOutput combinator with a predicate that filters out outputs greater than 2. As a result, the schedule stops producing outputs once the value exceeds 2, and the repetition ends.
Modifying
The Schedule.modifyDelay combinator allows you to adjust the delay of a schedule.
Example (Modified Delay Schedule)
Tapping
Whenever we need to effectfully process each schedule input/output, we can use Schedule.tapInput and Schedule.tapOutput.