Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptCombining schedules
The scheduling guide covers the basic schedule builders, retry, and repeat. This page covers the combinators that shape several schedules into one: capping a backoff, handing from one schedule to another, stopping on a budget or on the schedule's own output, and restarting the count after a healthy run.
Combine schedules
Schedule.union continues while either schedule continues and waits for the shorter delay. Combining exponential
backoff with a spaced schedule caps the backoff:
Show page setup
// Setup for the checked examples on this page.
open System
open Axial
open Axial.PlatformService
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actual
let runsUntilStopped (schedule: Schedule<ClockEnvironment, string, 'output>) : int =
let firstOutputs (count: int) (schedule: Schedule<ClockEnvironment, unit, 'output>) : 'output list =
match schedule |> FlowStream.fromSchedule |> FlowStream.take count |> FlowStream.runCollect |> Flow.run (ClockEnvironment Clock.live) with
| Exit.Success outputs -> outputs
| Exit.Failure _ -> []
let ms (value: float) = TimeSpan.FromMilliseconds value// Exponential back-off capped at 30 s, retrying forever
let reconnect : Schedule<ClockEnvironment, string, _> =
Schedule.exponential (TimeSpan.FromMilliseconds 200.0)
|> Schedule.union (Schedule.spaced (TimeSpan.FromSeconds 30.0))
reconnect: Schedule<ClockEnvironment,string,(TimeSpan option * int option)>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
union: Schedule<'env,'input,'otherOutput> -> Schedule<'env,'input,'output> -> Schedule<'env,'input,('output option * 'otherOutput option)>Continues while either schedule continues, waiting for the shorter of their delays. Both schedules are consulted at every decision. The output pairs their outputs; a side that has stopped contributes None, and the delay is then the continuing side's delay. The combined schedule stops when both have stopped. The schedule combined with the piped-in schedule; its output is the second element. The piped-in schedule; its output is the first element. // Exponential back-off capped at 30 s, retrying forever Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.union (Schedule.spaced (TimeSpan.FromSeconds 30.0))
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
FromSeconds: float -> TimeSpanReturns a that represents a specified number of seconds, where the specification is accurate to the nearest millisecond. A number of seconds, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open System (packages: Axial)val reconnect: Schedule<ClockEnvironment,string,(TimeSpan option * int option)>union continues while either side does and outputs both sides' outputs; a stopped side would show None:
firstOutputs 3 (Schedule.exponential (ms 1.0) |> Schedule.union (Schedule.spaced (ms 2.0)))
|> shouldEqual [ Some(ms 1.0), Some 0; Some(ms 2.0), Some 1; Some(ms 4.0), Some 2 ]
firstOutputs: int -> Schedule<ClockEnvironment,unit,'output> -> 'output listAxial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
ms: float -> TimeSpan(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
union: Schedule<'env,'input,'otherOutput> -> Schedule<'env,'input,'output> -> Schedule<'env,'input,('output option * 'otherOutput option)>Continues while either schedule continues, waiting for the shorter of their delays. Both schedules are consulted at every decision. The output pairs their outputs; a side that has stopped contributes None, and the delay is then the continuing side's delay. The combined schedule stops when both have stopped. The schedule combined with the piped-in schedule; its output is the second element. The piped-in schedule; its output is the first element. // Exponential back-off capped at 30 s, retrying forever Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.union (Schedule.spaced (TimeSpan.FromSeconds 30.0))
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
shouldEqual: 'a -> 'a -> unitSomeThe representation of "Value of type 'T" The input value. An option representing the value.
Requires & types
Requires: open Axial (packages: Axial)Schedule.intersect continues only while both schedules continue and waits for the longer delay. Combining a
recurrence limit with backoff bounds the number of attempts:
// At most 10 retries, with exponential back-off
let limitedBackoff : Schedule<ClockEnvironment, string, _> =
Schedule.recurs 10
|> Schedule.intersect (Schedule.exponential (TimeSpan.FromMilliseconds 200.0))
limitedBackoff: Schedule<ClockEnvironment,string,(int * TimeSpan)>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
recurs: int -> Schedule<'env,'input,int>Creates a schedule that recurs a fixed number of times. counts the schedule's own decisions, not the total number of flow executions: Flow.retry and Flow.repeat always run the source flow once before consulting the schedule at all, so recurs 3 means 3 additional retries/repeats on top of that one free attempt: 4 executions in total, not 3. A Schedule value carries no state of its own (the attempt count lives in the retry/repeat call), so the same schedule value is safe to reuse across independent runs. The maximum number of additional times to recur, on top of the source flow's one free initial attempt. A schedule that recurs up to times, emitting the current attempt count (0 to n-1). let retryThreeTimes () = Schedule.recurs 3 // Flow.retry runs the source flow once for free, then consults the schedule at // attempts 0, 1, 2 (three retries) before giving up: 4 executions in total.
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
intersect: Schedule<'env,'input,'otherOutput> -> Schedule<'env,'input,'output> -> Schedule<'env,'input,('output * 'otherOutput)>Continues while both schedules continue, waiting for the longer of their delays. The schedule combined with the piped-in schedule; its output is the second element. The piped-in schedule; its output is the first element. // At most 10 retries, with exponential back-off Schedule.recurs 10 |> Schedule.intersect (Schedule.exponential (TimeSpan.FromMilliseconds 200.0))
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open System (packages: Axial)val limitedBackoff: Schedule<ClockEnvironment,string,(int * TimeSpan)>runsUntilStopped (Schedule.recurs 10 |> Schedule.intersect (Schedule.exponential (TimeSpan.FromTicks 10L))) |> shouldEqual 11
runsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intAxial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
recurs: int -> Schedule<'env,'input,int>Creates a schedule that recurs a fixed number of times. counts the schedule's own decisions, not the total number of flow executions: Flow.retry and Flow.repeat always run the source flow once before consulting the schedule at all, so recurs 3 means 3 additional retries/repeats on top of that one free attempt: 4 executions in total, not 3. A Schedule value carries no state of its own (the attempt count lives in the retry/repeat call), so the same schedule value is safe to reuse across independent runs. The maximum number of additional times to recur, on top of the source flow's one free initial attempt. A schedule that recurs up to times, emitting the current attempt count (0 to n-1). let retryThreeTimes () = Schedule.recurs 3 // Flow.retry runs the source flow once for free, then consults the schedule at // attempts 0, 1, 2 (three retries) before giving up: 4 executions in total.
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
intersect: Schedule<'env,'input,'otherOutput> -> Schedule<'env,'input,'output> -> Schedule<'env,'input,('output * 'otherOutput)>Continues while both schedules continue, waiting for the longer of their delays. The schedule combined with the piped-in schedule; its output is the second element. The piped-in schedule; its output is the first element. // At most 10 retries, with exponential back-off Schedule.recurs 10 |> Schedule.intersect (Schedule.exponential (TimeSpan.FromMilliseconds 200.0))
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromTicks: int64 -> TimeSpanReturns a that represents a specified time, where the specification is in units of ticks. A number of ticks that represent a time. An object that represents .
shouldEqual: 'a -> 'a -> unitRequires & types
Requires: open Axial; open System (packages: Axial)Both pair the two schedules' outputs, with the piped-in schedule first. intersect emits 'output * 'otherOutput.
union emits 'output option * 'otherOutput option, because a side that has stopped has no output; it contributes
None while the other side continues.
Schedule.andThen runs one schedule until it stops, then hands over to another. The second schedule counts from zero
when it takes over, so its delays start from the beginning:
// Three quick retries, then up to five slower ones with backoff
let patient : Schedule<ClockEnvironment, string, _> =
Schedule.spaced (TimeSpan.FromMilliseconds 100.0)
|> Schedule.recursAtMost 3
|> Schedule.andThen (Schedule.exponential (TimeSpan.FromSeconds 1.0) |> Schedule.recursAtMost 5)
patient: Schedule<ClockEnvironment,string,Choice<int,TimeSpan>>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
recursAtMost: int -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Stops a schedule after at most recurrences, keeping its output and delays. Like recurs, counts recurrences after the first run, so recursAtMost 3 with Flow.retry allows 4 executions in total. The maximum number of recurrences. The schedule to cap. Schedule.exponential (TimeSpan.FromMilliseconds 100.0) |> Schedule.recursAtMost 5
andThen: Schedule<'env,'input,'nextOutput> -> Schedule<'env,'input,'output> -> Schedule<'env,'input,Choice<'output,'nextOutput>>Runs until it stops, then continues with . starts counting from zero when it takes over, so recurs and exponential behave as if it were the only schedule. The output says which phase decided. The schedule that takes over once the first stops. The schedule that runs first. // Three quick retries, then slower ones Schedule.spaced (TimeSpan.FromMilliseconds 100.0) |> Schedule.recursAtMost 3 |> Schedule.andThen (Schedule.exponential (TimeSpan.FromSeconds 1.0) |> Schedule.recursAtMost 5)
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
FromSeconds: float -> TimeSpanReturns a that represents a specified number of seconds, where the specification is accurate to the nearest millisecond. A number of seconds, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open System (packages: Axial)val patient: Schedule<ClockEnvironment,string,Choice<int,TimeSpan>>Schedule.spaced (ms 0.1)
|> Schedule.recursAtMost 3
|> Schedule.andThen (Schedule.exponential (ms 0.1) |> Schedule.recursAtMost 5)
|> runsUntilStopped
|> shouldEqual 9
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
spaced: TimeSpan -> Schedule<'env,'input,int>Creates a schedule that recurs with a fixed delay between attempts. The fixed time span to wait between each attempt. A schedule that recurs indefinitely with the specified fixed delay, emitting the current attempt count. Thrown when is negative. let everySecond () = Schedule.spaced (TimeSpan.FromSeconds 1.0)
ms: float -> TimeSpan(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
recursAtMost: int -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Stops a schedule after at most recurrences, keeping its output and delays. Like recurs, counts recurrences after the first run, so recursAtMost 3 with Flow.retry allows 4 executions in total. The maximum number of recurrences. The schedule to cap. Schedule.exponential (TimeSpan.FromMilliseconds 100.0) |> Schedule.recursAtMost 5
andThen: Schedule<'env,'input,'nextOutput> -> Schedule<'env,'input,'output> -> Schedule<'env,'input,Choice<'output,'nextOutput>>Runs until it stops, then continues with . starts counting from zero when it takes over, so recurs and exponential behave as if it were the only schedule. The output says which phase decided. The schedule that takes over once the first stops. The schedule that runs first. // Three quick retries, then slower ones Schedule.spaced (TimeSpan.FromMilliseconds 100.0) |> Schedule.recursAtMost 3 |> Schedule.andThen (Schedule.exponential (TimeSpan.FromSeconds 1.0) |> Schedule.recursAtMost 5)
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
runsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intshouldEqual: 'a -> 'a -> unitRequires & types
Requires: open Axial (packages: Axial)Its output is Choice1Of2 while the first schedule decides and Choice2Of2 after the hand-over.
Stop on time or on the schedule's own output
Schedule.upTo gives a schedule a total time budget, measured from when the first run began. The schedule continues
while less than the budget has passed; it never cuts a run short. To cap the number of retries instead, use
Schedule.recursAtMost:
// Retry with backoff, but give up after two minutes in total
let bounded : Schedule<ClockEnvironment, string, _> =
Schedule.exponential (TimeSpan.FromMilliseconds 200.0)
|> Schedule.upTo (TimeSpan.FromMinutes 2.0)
bounded: Schedule<ClockEnvironment,string,TimeSpan>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
upTo: TimeSpan -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Stops a schedule once has passed since the first run began. Time is measured with the runtime's clock. The schedule continues while the elapsed time is below the budget, so a retry that is already scheduled still runs even if its delay ends a little past the budget; a run in progress is never cut short. Use Flow.timeout to bound a single run, and recursAtMost to cap the number of recurrences instead. The total time the schedule may keep recurring. The schedule to bound. // Retry with backoff, but give up after two minutes in total Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.upTo (TimeSpan.FromMinutes 2.0)
FromMinutes: float -> TimeSpanReturns a that represents a specified number of minutes, where the specification is accurate to the nearest millisecond. A number of minutes, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open System (packages: Axial)val bounded: Schedule<ClockEnvironment,string,TimeSpan>With a 20 ms budget, delays of 1, 2, 4, 8, and 16 ms allow at most six runs:
let budgeted = runsUntilStopped (Schedule.exponential (ms 1.0) |> Schedule.upTo (ms 20.0))
(budgeted >= 2 && budgeted <= 6) |> shouldEqual true
budgeted: intrunsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intAxial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
ms: float -> TimeSpan(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
upTo: TimeSpan -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Stops a schedule once has passed since the first run began. Time is measured with the runtime's clock. The schedule continues while the elapsed time is below the budget, so a retry that is already scheduled still runs even if its delay ends a little past the budget; a run in progress is never cut short. Use Flow.timeout to bound a single run, and recursAtMost to cap the number of recurrences instead. The total time the schedule may keep recurring. The schedule to bound. // Retry with backoff, but give up after two minutes in total Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.upTo (TimeSpan.FromMinutes 2.0)
(>=): 'T -> 'T -> boolStructural greater-than-or-equal The first parameter. The second parameter. The result of the comparison. 5 >= 1 // Evaluates to true 5 >= 5 // Evaluates to true [1; 5] >= [1; 6] // Evaluates to false
(&&): bool -> bool -> boolBinary 'and'. When used as a binary operator the right hand value is evaluated only on demand The first value. The second value. The result of the operation.
(<=): 'T -> 'T -> boolStructural less-than-or-equal comparison The first parameter. The second parameter. The result of the comparison. 5 <= 1 // Evaluates to false 5 <= 5 // Evaluates to true [1; 5] <= [1; 6] // Evaluates to true
shouldEqual: 'a -> 'a -> unitRequires & types
Requires: open Axial (packages: Axial)val budgeted: intSchedule.whileOutput and Schedule.untilOutput stop on what the schedule itself produces, where whileInput looks
at the error or value being retried. For example, stop once exponential backoff reaches 30 seconds:
let untilSlow : Schedule<ClockEnvironment, string, _> =
Schedule.exponential (TimeSpan.FromMilliseconds 200.0)
|> Schedule.whileOutput (fun delay -> delay < TimeSpan.FromSeconds 30.0)
untilSlow: Schedule<ClockEnvironment,string,TimeSpan>Axial.Schedule`3Decides, after each execution of a flow, whether to run it again and how long to wait first. A schedule value holds no state, so the same value can drive any number of independent runs.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
System.TimeSpanRepresents a time interval.
FromMilliseconds: float -> TimeSpanReturns a that represents a specified number of milliseconds. A number of milliseconds. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
whileOutput: ('output -> bool) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Continues only while the schedule's output satisfies a predicate. Where whileInput looks at the error or value being retried, this looks at what the schedule itself produced, such as the next backoff delay or the retry count. Returns true for outputs that may recur. The schedule whose output is checked. // Back off exponentially, but stop once the delay would reach 30 seconds Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.whileOutput (fun delay -> delay < TimeSpan.FromSeconds 30.0)
delay: TimeSpan(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
FromSeconds: float -> TimeSpanReturns a that represents a specified number of seconds, where the specification is accurate to the nearest millisecond. A number of seconds, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
Requires & types
Requires: open Axial; open System (packages: Axial)val untilSlow: Schedule<ClockEnvironment,string,TimeSpan>runsUntilStopped (Schedule.exponential (ms 1.0) |> Schedule.whileOutput (fun delay -> delay < ms 8.0)) |> shouldEqual 4
runsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intAxial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
exponential: TimeSpan -> Schedule<'env,'input,TimeSpan>Creates a schedule that recurs with exponential backoff. The initial delay for the first retry. A schedule that recurs indefinitely, doubling the delay each time (baseDelay * 2^attempt) and capping at instead of overflowing. Thrown when is negative. let backoff () = Schedule.exponential (TimeSpan.FromMilliseconds 100.0) // Delays: 100ms, 200ms, 400ms, 800ms...
ms: float -> TimeSpan(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
whileOutput: ('output -> bool) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Continues only while the schedule's output satisfies a predicate. Where whileInput looks at the error or value being retried, this looks at what the schedule itself produced, such as the next backoff delay or the retry count. Returns true for outputs that may recur. The schedule whose output is checked. // Back off exponentially, but stop once the delay would reach 30 seconds Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.whileOutput (fun delay -> delay < TimeSpan.FromSeconds 30.0)
delay: TimeSpan(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
shouldEqual: 'a -> 'a -> unitRequires & types
Requires: open Axial (packages: Axial)Schedule.elapsed recurs without waiting and emits the time since the first run, and Schedule.map transforms any
schedule's output.
Restart the count after a long run
Schedule.resetAfter restarts the wrapped schedule's count when the run that just finished lasted at least the given
time. The threshold measures that run's own duration, not a previous successful execution: with Flow.supervise it is
how long the worker ran before it crashed. When the threshold is met, the wrapped schedule sees attempt 0 again, so
recurs and exponential restart too:
// Five restarts for a crash loop, but a crash after ten minutes of good work gets the full budget back.
let worker : Flow<ClockEnvironment, Never, unit> =
flow {
do! Flow.sleep (TimeSpan.FromMinutes 10.0)
return! Flow.die (InvalidOperationException "crash")
}
let supervisedWorker =
worker |> Flow.supervise (Schedule.recurs 5 |> Schedule.resetAfter (TimeSpan.FromMinutes 10.0))
worker: Flow<ClockEnvironment,Never,unit>Axial.Flow`3Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.NeverRepresents an error channel that cannot occur.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
Axial.Flowsleep: TimeSpan -> Flow<'env,'error,unit>Suspends the flow for the specified duration, observing cancellation. The duration to sleep. A flow that completes after the specified delay, or is interrupted if cancelled first.
System.TimeSpanRepresents a time interval.
FromMinutes: float -> TimeSpanReturns a that represents a specified number of minutes, where the specification is accurate to the nearest millisecond. A number of minutes, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
die: exn -> Flow<'env,'error,'value>Creates a defective flow that fails with an exception. The exception representing the defect. A flow that always dies with the provided exception. This is the public constructor for non-domain defects. Use fail for expected typed failures and die when the workflow should surface a bug or panic.
``.ctor``: string -> unitInitializes a new instance of the class with a specified error message. The message that describes the error.
supervisedWorker: Flow<ClockEnvironment,Never,unit>(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
supervise: Schedule<'env,exn,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Restarts a flow that terminates with an unexpected defect, according to a schedule. The defect-channel sibling of retry: retry re-runs typed Cause.Fail errors and never touches defects, while supervise re-runs Cause.Die defects and never touches typed errors or interruptions. The schedule sees the first defect of each failed run. Each attempt runs in its own child scope, closed before the next attempt starts, so finalizers registered by a failed attempt are released instead of accumulating. Re-evaluation only resets state that lives inside the flow itself; mutable state in the environment is not restored. When the schedule stops, the final defect propagates as the flow's exit. Decides, from each defect, whether to restart and after what delay. The flow to supervise. A flow that re-evaluates Cause.Die outcomes while the schedule allows it. worker |> Flow.supervise (Retry.schedule { Retry.defaults with Retries = 5 })
Axial.ScheduleModuleBuilds and combines schedules that decide whether, and after what delay, a flow runs again. Drive a schedule with Flow.retry (after typed failures), Flow.repeat (after successes), or FlowStream.fromSchedule (as a stream of its outputs). A schedule holds no state, so one value can drive any number of independent runs.
recurs: int -> Schedule<'env,'input,int>Creates a schedule that recurs a fixed number of times. counts the schedule's own decisions, not the total number of flow executions: Flow.retry and Flow.repeat always run the source flow once before consulting the schedule at all, so recurs 3 means 3 additional retries/repeats on top of that one free attempt: 4 executions in total, not 3. A Schedule value carries no state of its own (the attempt count lives in the retry/repeat call), so the same schedule value is safe to reuse across independent runs. The maximum number of additional times to recur, on top of the source flow's one free initial attempt. A schedule that recurs up to times, emitting the current attempt count (0 to n-1). let retryThreeTimes () = Schedule.recurs 3 // Flow.retry runs the source flow once for free, then consults the schedule at // attempts 0, 1, 2 (three retries) before giving up: 4 executions in total.
resetAfter: TimeSpan -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Starts a schedule's count again after a run that lasted at least . A long run means the failure that follows is new, not a continuation of earlier ones. With Flow.supervise, a worker that crashes after an hour of good work gets its full restart budget back instead of having used it up on crashes from the day before. The wrapped schedule then sees attempt 0 again, so recurs and exponential restart too. How long a run must last for the count to restart. The schedule whose count restarts. worker |> Flow.supervise (Schedule.recurs 5 |> Schedule.resetAfter (TimeSpan.FromMinutes 10.0))
Requires & types
Requires: open Axial; open System (packages: Axial)val worker: Flow<ClockEnvironment,Never,unit>val supervisedWorker: Flow<ClockEnvironment,Never,unit>The full signature list for every combinator is in the Schedule API reference.

