Skip to content

Effect scheduling improvements

Compare
Choose a tag to compare
@louthy louthy released this 07 Jun 19:57
· 448 commits to main since this release

The existing Schedule type has been massively upgraded to support even more complex scheduling for repeating, retrying, and folding of Aff and Eff types.

A huge thanks to @bmazzarol who did all of the heavy lifting to make this feature a reality!

It has been refactored from the ground up, a Schedule now is a (possibly infinite) stream of durations. Each duration indicates to the retry, repeat, and fold behaviours how long to wait between each action. The generation of those streams comes from:

  • Schedule.Forever - infinite stream of zero length durations
  • Schedule.Once - one item stream of zero length duration
  • Schedule.Never - no durations (a schedule that never runs)
  • Schedule.TimeSeries(1, 2, 3 ...) - pass in your own durations to build a bespoke schedule
  • Schedule.spaced(space) - infinite stream of space length durations
  • Schedule.linear(seed, factor) - schedule that recurs continuously using a linear back-off
  • Schedule.exponential(seed, factor) - schedule that recurs continuously using a exponential back-off
  • Schedule.fibonacci(seed, factor) - schedule that recurs continuously using a fibonacci based back-off
  • Schedule.upto(max) - schedule that runs for a given duration
  • Schedule.fixedInterval(interval) - if that action run between updates takes longer than the interval, then the action will run immediately
  • Schedule.windowed(interval) - a schedule that divides the timeline into interval-long windows, and sleeps until the nearest window boundary every time it recurs.
  • Schedule.secondOfMinute(second) - a schedule that recurs every specified second of each minute
  • Schedule.minuteOfHour(minute) - a schedule that recurs every specified minute of each hour
  • Schedule.hourOfDay(hour) - a schedule that recurs every specified hour of each day
  • Schedule.dayOfWeek(day) - a schedule that recurs every specified day of each week

These schedules are mostly infinite series, and so to control their 'length' we compose with ScheduleTransformer values to create smaller series, or to manipulate the series in some way (jitter for example). The following functions generate ScheduleTransformer values.

  • Schedule.recurs(n) - Clamps the schedule durations to only recur n times.
  • Schedule.NoDelayOnFirst - Regardless of any other settings, it makes the first duration zero
  • Schedule.RepeatForever - Repeats any composed schedules forever
  • Schedule.maxDelay(max) - limits the returned delays to max delay (upper clamping of durations).
  • Schedule.maxCumulativeDelay(Duration max) - keeps a tally of all the delays so-far, and ends the generation of the series once max delay has passed
  • Schedule.jitter(minRandom, maxRandom, seed) - adds random jitter to the durations
  • Schedule.jitter(factor, seed) - adds random jitter to the durations
  • Schedule.decorrelate(factor, seed) - transforms the schedule by de-correlating each of the durations both up and down in a jittered way.
  • Schedule.resetAfter(max) - resets the schedule after a provided cumulative max duration
  • Schedule.repeats(n) - not to be confused with recurs, this repeats the schedule n times.
  • Schedule.intersperse(schedule) - intersperse the provided schedule between each duration in the schedule.

Schedule and ScheduleTransformer can be composed using | (union) or & (intersection):

    var schedule = Schedule.linear(1 * sec) | Schedule.recurs(3) | Schedule.repeat(3);

    // [1s, 2s, 3s, 1s, 2s, 3s, 1s, 2s, 3s]

Union | will take the minimum of the two schedules to the length of the longest, intersect & will take the maximum of the two schedules to the length of the shortest.

One thing remaining to-do is to bring HasTime<RT> back into the Core and allow these schedules to use injectable time. Some of the functions already take a Func<DateTime> to access 'now', this will be expanded so time can be sped up or slowed down, with the schedules 'just working'. That'll be in the next few weeks I'm sure, and is related to this issue.

Check out the API documentation to see what's what. And again, thanks to @bmazzarol for the hard work 👍