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.JavaScriptScheduling and Retries
A Schedule decides whether a flow should run again and how long to wait. Creating a schedule doesn't run anything. Apply it with Flow.retry or Flow.repeat.
Use schedules for tasks such as retrying a request, adding exponential backoff, polling a service, or running a heartbeat.
Schedules don't store attempt state. Flow.retry and Flow.repeat track attempts for each run, so you can reuse one schedule value across unrelated flows.
Schedule works on .NET and Fable's JavaScript target.
How schedules work
A schedule makes two decisions after each flow execution:
- Whether to run the flow again.
- How long to wait before the next run.
The source flow always runs once before Axial consults the schedule. For example, Schedule.recurs 3 allows three more runs, for up to four runs in total.
The examples on this page check each schedule with two helpers. runsUntilStopped counts how many times a flow that
always fails runs under a schedule. firstOutputs reads a schedule's first outputs as a stream. Their delays really
happen, so the checks use millisecond versions of the schedules they describe:
Shared setup
// Setup for the checked examples on this page.
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.JavaScript
/// 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
SystemIOThreadingTasksAxialLayersConsoleFileSystemHostingBrowserNodePlatformServiceStateTelemetryJavaScriptshouldEqual: 'a -> 'a -> unitexpected: 'aactual: 'a(<>): 'T -> 'T -> boolStructural inequality The first parameter. The second parameter. The result of the comparison. 5 <> 5 // Evaluates to false 5 <> 6 // Evaluates to true [1; 2] <> [1; 2] // Evaluates to false
failwithf: Printf.StringFormat<'T,'Result> -> 'TPrint to a string buffer and raise an exception with the given result. Helper printers must return strings. The formatter. The formatted result. See Printf.failwithf (link: ) for examples.
let runsUntilStopped (schedule: Schedule<ClockEnvironment, string, 'output>) : int =
let runs = ref 0
(Flow.delay (fun () ->
runs.Value <- runs.Value + 1
Flow.fail "always")
: Flow<ClockEnvironment, string, unit>)
|> Flow.retry schedule
|> Flow.run (ClockEnvironment Clock.live)
|> ignore
runs.Value
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
runsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intschedule: Schedule<ClockEnvironment,string,'output>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
outputintAn abbreviation for the CLI type . Basic Types
runs: int refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
Axial.Flowdelay: (unit -> Flow<'env,'error,'value>) -> Flow<'env,'error,'value>Defers flow construction until execution time. A function that returns the flow to execute. A flow that lazily evaluates the factory when executed. let flow = Flow.delay (fun () -> Flow.succeed 42)
Value: intThe current value of the reference cell
(+): ^T1 -> ^T2 -> ^T3Overloaded addition operator The first parameter. The second parameter. The result of the operation. 2 + 2 // Evaluates to 4 "Hello " + "World" // Evaluates to "Hello World"
fail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
(|>): '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
retry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
run: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
ignore: 'T -> unitIgnore the passed value. This is often used to throw away results of a computation. The value to ignore. ignore 55555 // Evaluates to ()
firstOutputs: int -> Schedule<ClockEnvironment,unit,'output> -> 'output listcount: intschedule: Schedule<ClockEnvironment,unit,'output>listThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
Axial.FlowStreamModulefromSchedule: Schedule<'env,unit,'output> -> FlowStream<'env,'error,'output>Creates a stream that emits a schedule's outputs, each after the delay the schedule chooses. The stream ends when the schedule stops. Delays are measured on the runtime's time, and a slow consumer does not shift a fixed-rate schedule: fromSchedule (Schedule.fixedRate period) emits on the start + n * period grid, skipping ticks the consumer was too busy to take instead of bursting. // A tick every 50 ms, aligned to when the stream started. Schedule.fixedRate (TimeSpan.FromMilliseconds 50.0) |> FlowStream.fromSchedule
take: int -> FlowStream<'a,'b,'c> -> FlowStream<'a,'b,'c>Emits at most values. stream |> FlowStream.take 10
runCollect: FlowStream<'a,'b,'c> -> Flow<'a,'b,'c list>Collects all emitted values into a list. stream |> FlowStream.runCollect
Axial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
outputs: 'output listFailureThe workflow failed due to a specific cause.
ms: float -> TimeSpanvalue: floatfloatAn abbreviation for the CLI type . Basic Types
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 .
Build a schedule
Choose a schedule based on how many times the flow can run and how long Axial should wait between runs.
Limit the number of recurrences
Use Schedule.recurs to set the number of additional runs.
// Run up to 6 times: 1 initial run and 5 additional runs.
let fiveMoreTimes = Schedule.recurs 5
fiveMoreTimes: Schedule<ClockEnvironment,string,int>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.
runsUntilStopped fiveMoreTimes |> shouldEqual 6
runsUntilStopped: Schedule<ClockEnvironment,string,'output> -> intfiveMoreTimes: Schedule<ClockEnvironment,string,int>(|>): '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
shouldEqual: 'a -> 'a -> unitUse a fixed delay
Use Schedule.spaced to keep running with the same delay between runs.
// Wait 1 second between runs.
let everySecond : Schedule<ClockEnvironment, string, _> = Schedule.spaced (TimeSpan.FromSeconds 1.0)
everySecond: Schedule<ClockEnvironment,string,int>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.
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 .
spaced never stops on its own, and its output counts the runs so far:
firstOutputs 3 (Schedule.spaced (ms 1.0)) |> shouldEqual [ 0; 1; 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.
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
shouldEqual: 'a -> 'a -> unitA spaced schedule doesn't stop on its own. The flow continues until it fails, is interrupted, or an outer operation stops it.
Use exponential backoff
Use Schedule.exponential when repeated attempts should wait progressively longer.
// Wait 100 ms, 200 ms, 400 ms, 800 ms, and so on.
let backoff : Schedule<ClockEnvironment, string, _> = Schedule.exponential (TimeSpan.FromMilliseconds 100.0)
backoff: 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 .
Its output is the delay it chose:
firstOutputs 4 (Schedule.exponential (ms 1.0)) |> shouldEqual [ ms 1.0; ms 2.0; ms 4.0; ms 8.0 ]
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
shouldEqual: 'a -> 'a -> unitAdd jitter
If many clients retry at the same time, they can place another burst of load on the service. Jitter spreads those retries across a wider period.
Schedule.jitteredWith multiplies each delay by a sampled factor from 0.5 to 1.5. You provide the sample function, which keeps randomness explicit and replaceable in tests.
let policy : Schedule<ClockEnvironment, string, _> =
Schedule.exponential (TimeSpan.FromMilliseconds 100.0)
|> Schedule.jitteredWith (System.Random().NextDouble)
policy: 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
jitteredWith: (unit -> float) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Adds jitter to a schedule's delay using a caller-supplied sample source. is not validated: a value outside [0.0, 1.0) is not rejected, it just produces a jitter factor outside the documented 0.5–1.5 range. The result is still always a valid, non-negative TimeSpan: negative factors clamp to and overflowing ones clamp to , the same as the base schedule's own overflow handling. This is deliberate: jitteredWith never throws for a badly-behaved sample source. A function returning a value in [0.0, 1.0), sampled once per attempt. Supply a deterministic function for reproducible schedules and tests. The base schedule to which jitter will be applied. A new schedule where each delay is multiplied by sample () + 0.5, giving a factor between 0.5 and 1.5, capped at . let schedule = Schedule.spaced (TimeSpan.FromSeconds 1.0) |> Schedule.jitteredWith (fun () -> 0.25) // Every delay becomes 750ms.
System``.ctor``: unit -> unitInitializes a new instance of the class, using a time-dependent default seed value.
NextDouble: unit -> floatReturns a random floating-point number that is greater than or equal to 0.0, and less than 1.0. A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0.
In application code, get the sample function from the IRandom service in Axial.PlatformService. In tests, replace it with a function that returns a fixed value.
Run at a fixed rate
Schedule.spaced waits a fixed delay after each run, so a run that takes 15 ms on a 50 ms spacing starts every 65 ms.
Schedule.fixedRate instead aligns runs to when the first run began: they start at start + n * period, however long
each takes.
// Start a control scan every 50 ms without drifting.
let scanOnce : Flow<ClockEnvironment, Never, unit> = Flow.ok ()
let scanLoop =
scanOnce
|> Flow.repeat (Schedule.fixedRate (TimeSpan.FromMilliseconds 50.0))
scanOnce: Flow<ClockEnvironment,Never,unit>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.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
Axial.Flowok: 'value -> Flow<'env,'error,'value>Creates a successful synchronous flow. The value to wrap in a successful flow. A flow that always succeeds with the provided value.
scanLoop: 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
repeat: Schedule<'env,'value,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Repeats a successful flow according to a schedule. The flow runs once, and after each success the schedule sees the value and decides whether to run it again and how long to wait. Any failure stops the repetition immediately. When the schedule stops, the flow succeeds with the last value. Each run has its own child scope, closed before the next run starts, so a repetition that lasts the life of an application does not accumulate finalizers. Decides, from each value, whether to repeat and after what delay. The flow to repeat. A flow that succeeds with the value of the last run. heartbeat |> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0))
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.
fixedRate: TimeSpan -> Schedule<'env,'input,int>Recurs at a fixed rate aligned to when the first run began, emitting the recurrence count. Runs start at start + n * period, so the time a run takes does not shift later runs. A run that takes longer than a period is followed by one immediate run, after which the schedule realigns to the next boundary; missed ticks are skipped rather than run in a burst. Thrown when is not positive. scanOnce |> Flow.repeat (Schedule.fixedRate (TimeSpan.FromMilliseconds 50.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 .
If a run takes longer than a period, the next run starts immediately, once. The schedule then realigns to the next boundary, so missed ticks are skipped rather than run in a burst.
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:
// 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 .
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.
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 .
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 -> unitBoth 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 .
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 -> unitOne initial run, three quick retries, then five slower ones.
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 .
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 -> unitSchedule.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 .
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 -> unitSchedule.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 healthy run
Schedule.resetAfter treats a failure that follows a long, successful run as a new problem. When a run lasted at
least the given time, the wrapped schedule starts counting from zero again, so recurs and exponential restart too:
// Five restarts for a crash loop, but a crash after ten healthy minutes gets the full budget back
let worker : Flow<ClockEnvironment, Never, unit> = Flow.ok ()
let supervisedWorker =
worker |> Flow.supervise (Schedule.recurs 5 |> Schedule.resetAfter (TimeSpan.FromMinutes 10.0))
worker: Flow<ClockEnvironment,Never,unit>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.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
Axial.Flowok: 'value -> Flow<'env,'error,'value>Creates a successful synchronous flow. The value to wrap in a successful flow. A flow that always succeeds with the provided value.
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))
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 .
Retry failed flows
Use Flow.retry to rerun a flow after an expected domain failure (Cause.Fail).
Flow.retry doesn't retry defects (Cause.Die) or interruptions (Cause.Interrupt). Axial passes them through without consulting the schedule.
let callsMade = ref 0
let unstableCall : Flow<ClockEnvironment, string, unit> =
flow {
callsMade.Value <- callsMade.Value + 1
return! Flow.fail "temporary-error"
}
// Try up to 4 times: 1 initial attempt and 3 retries.
let resilientCall =
unstableCall
|> Flow.retry (Schedule.recurs 3)
callsMade: int refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
unstableCall: Flow<ClockEnvironment,string,unit>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
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.
Value: intThe current value of the reference cell
(+): ^T1 -> ^T2 -> ^T3Overloaded addition operator The first parameter. The second parameter. The result of the operation. 2 + 2 // Evaluates to 4 "Hello " + "World" // Evaluates to "Hello World"
Axial.Flowfail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
resilientCall: Flow<ClockEnvironment,string,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
retry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
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.
resilientCall |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Failure(Cause.Fail "temporary-error"))
callsMade.Value |> shouldEqual 4
resilientCall: Flow<ClockEnvironment,string,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
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
FailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
callsMade: int refValue: intThe current value of the reference cell
The retry stops when the flow succeeds or the schedule declines another run. If the schedule stops after a failure, the flow returns that failure.
Each attempt runs in its own child scope. When an attempt fails and another is scheduled, the failed attempt's finalizers run before the retry starts. The successful attempt's scope stays open until the enclosing scope closes, so a connection or handle it returns is still usable.
Retry only some errors
The schedule sees each typed error as its input. Schedule.whileInput continues only for errors that satisfy a
predicate, and Schedule.recursAtMost caps the number of retries of any schedule:
type FetchError =
| Unavailable
| RateLimited
| NotFound
/// A fetch that fails with each error in turn, then succeeds.
let fetchFailing (errors: FetchError list) : Flow<ClockEnvironment, FetchError, string> =
let remaining = ref errors
Flow.delay (fun () ->
match remaining.Value with
| error :: rest ->
remaining.Value <- rest
Flow.fail error
| [] -> Flow.ok "payload")
let isTransient error =
match error with
| Unavailable
| RateLimited -> true
| NotFound -> false
let resilientFetch (fetch: Flow<ClockEnvironment, FetchError, string>) =
fetch
|> Flow.retry (
Schedule.exponential (TimeSpan.FromMilliseconds 200.0)
|> Schedule.recursAtMost 3
|> Schedule.whileInput isTransient)
FsLiveDocsGeneratedPage0_7555C511A9CE.FetchErrorUnavailableRateLimitedNotFoundfetchFailing: FetchError list -> Flow<ClockEnvironment,FetchError,string>errors: FetchError listlistThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
stringAn abbreviation for the CLI type . Basic Types
remaining: FetchError list refref: 'T -> 'T refCreate a mutable reference cell The value to contain in the cell. The created reference cell. let count = ref 0 // Creates a reference cell object with a mutable Value property count.Value // Evaluates to 0 count.Value <- 1 // Updates the value count.Value // Evaluates to 1
Axial.Flowdelay: (unit -> Flow<'env,'error,'value>) -> Flow<'env,'error,'value>Defers flow construction until execution time. A function that returns the flow to execute. A flow that lazily evaluates the factory when executed. let flow = Flow.delay (fun () -> Flow.succeed 42)
Value: FetchError listThe current value of the reference cell
error: FetchError(::)rest: FetchError listfail: 'error -> Flow<'env,'error,'value>Same as error. The error value to wrap in a failing flow. A flow that always fails with the provided error. let result = Flow.fail "error" |> Flow.run () // result = Failure (Cause.Fail "error")
ok: 'value -> Flow<'env,'error,'value>Creates a successful synchronous flow. The value to wrap in a successful flow. A flow that always succeeds with the provided value.
isTransient: FetchError -> boolresilientFetch: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>fetch: Flow<ClockEnvironment,FetchError,string>(|>): '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
retry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
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 .
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
whileInput: ('input -> bool) -> Schedule<'env,'input,'output> -> Schedule<'env,'input,'output>Continues only while the input satisfies a predicate. With Flow.retry the input is the typed error, so this selects which errors are worth retrying; an error that fails the predicate stops the schedule and the flow fails with it. With Flow.repeat the input is the successful value. Returns true for inputs that may recur. The schedule consulted for inputs that satisfy the predicate. Schedule.exponential (TimeSpan.FromMilliseconds 200.0) |> Schedule.recursAtMost 3 |> Schedule.whileInput HttpError.isTransient
fetchFailing [ Unavailable; RateLimited ] |> resilientFetch |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success "payload")
fetchFailing [ Unavailable; NotFound ] |> resilientFetch |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Failure(Cause.Fail NotFound))
fetchFailing: FetchError list -> Flow<ClockEnvironment,FetchError,string>UnavailableRateLimited(|>): '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
resilientFetch: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
NotFoundFailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
NotFound is not transient, so the retry stops there even though retries remain.
Schedule.untilInput is the negation: it stops on the first input that satisfies the predicate.
Describe a retry with a record
A Retry record names the three choices most retries make, and Retry.schedule turns it into a schedule. Start
from Retry.defaults (3 retries, exponential backoff from 100 ms capped at 10 s, every error retried) and change
the fields you need:
let resilientFetchWithRecord (fetch: Flow<ClockEnvironment, FetchError, string>) =
fetch
|> Flow.retry (Retry.schedule { Retry.defaults with Retries = 5; When = isTransient; Backoff = Backoff.NoDelay })
resilientFetchWithRecord: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>fetch: Flow<ClockEnvironment,FetchError,string>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
FsLiveDocsGeneratedPage0_7555C511A9CE.FetchErrorstringAn abbreviation for the CLI type . Basic Types
(|>): '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
Axial.Flowretry: Schedule<'env,'error,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Retries a flow's typed failures according to a schedule. The flow runs once, and after each Cause.Fail the schedule sees the error and decides whether to run it again and how long to wait. Defects and interruptions are never retried. When the schedule stops, the flow fails with the last error. Each attempt runs in its own child scope; a failed attempt's finalizers run before the next attempt starts. For the common case, build the schedule from a Retry record. Decides, from each typed error, whether to retry and after what delay. The flow to retry. A flow that succeeds as soon as an attempt succeeds. fetch |> Flow.retry (Schedule.recurs 3) fetch |> Flow.retry (Retry.schedule { Retry.defaults with When = HttpError.isTransient })
Axial.RetryModuleConstructors for .
schedule: Retry<'input> -> Schedule<'env,'input,int>Builds the schedule a retry record describes. The retry description. A schedule that emits the retry number, starting at 0. Thrown when Retries is negative or a delay is negative.
Axial.Retry`1A retry described with named fields: how many retries, how long to wait, and which inputs to retry. Build one from Retry.defaults with record update syntax and pass it to Flow.retry through Retry.schedule. It is a shorthand for the common case; anything a record cannot express (jitter, elapsed-time limits, fixed-rate runs) is written as a Schedule directly. The input the retry decision sees: the typed error for Flow.retry, the defect for Flow.supervise.
defaults: Retry<'input>Three retries with exponential backoff from 100 ms, capped at 10 s, retrying every input. fetch |> Flow.retry (Retry.schedule { Retry.defaults with Retries = 5; When = HttpError.isTransient })
Retries: intThe maximum number of retries after the first attempt; 3 allows 4 executions in total.
When: 'input -> boolReturns true for inputs that should be retried; others stop immediately.
Backoff: BackoffThe delay before each retry.
fetchFailing [ RateLimited; RateLimited; Unavailable ] |> resilientFetchWithRecord |> Flow.run (ClockEnvironment Clock.live) |> shouldEqual (Exit.Success "payload")
fetchFailing: FetchError list -> Flow<ClockEnvironment,FetchError,string>RateLimitedUnavailable(|>): '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
resilientFetchWithRecord: Flow<ClockEnvironment,FetchError,string> -> Flow<ClockEnvironment,FetchError,string>Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
``.ctor``: IClock -> ClockEnvironmentAxial.PlatformService.ClockHelpers for the clock service.
live: IClockCreates a live clock backed by and a monotonic timer.
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
Retries counts retries after the first attempt, the same as Schedule.recurs. Backoff is Backoff.NoDelay,
Backoff.Fixed delay, or Backoff.Exponential(initial, max). Write a Schedule pipeline when you need jitter,
fixed-rate timing, or elapsed-time limits.
Repeat successful flows
Use Flow.repeat to run a successful flow again. This is useful for polling, heartbeats, and recurring background work.
let pollStatus : Flow<ClockEnvironment, Never, string> =
flow {
return "Still working"
}
// Poll every 5 seconds until the flow fails or is interrupted.
let recurringPoll =
pollStatus
|> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0))
pollStatus: Flow<ClockEnvironment,Never,string>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.ClockEnvironmentAn environment containing only a clock, for timed flows with no other services.
Axial.NeverRepresents an error channel that cannot occur.
stringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
recurringPoll: Flow<ClockEnvironment,Never,string>(|>): '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
Axial.Flowrepeat: Schedule<'env,'value,'output> -> Flow<'env,'error,'value> -> Flow<'env,'error,'value>Repeats a successful flow according to a schedule. The flow runs once, and after each success the schedule sees the value and decides whether to run it again and how long to wait. Any failure stops the repetition immediately. When the schedule stops, the flow succeeds with the last value. Each run has its own child scope, closed before the next run starts, so a repetition that lasts the life of an application does not accumulate finalizers. Decides, from each value, whether to repeat and after what delay. The flow to repeat. A flow that succeeds with the value of the last run. heartbeat |> Flow.repeat (Schedule.spaced (TimeSpan.FromSeconds 5.0))
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.
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 .
Flow.repeat consults the schedule only after a successful run. A typed failure, defect, or interruption stops the repetition immediately.
Drive a stream from a schedule
FlowStream.fromSchedule turns a schedule into a stream of its outputs, each emitted after the delay the schedule
chooses. It ends when the schedule stops. With Schedule.fixedRate it is a tick stream that keeps to its grid even
when the consumer is slow: ticks the consumer was too busy to take are skipped, not delivered in a burst.
> open Axial.PlatformService;;
> (Schedule.fixedRate (System.TimeSpan.FromMilliseconds 20.0)
- |> FlowStream.fromSchedule
- |> FlowStream.take 3
- |> FlowStream.runCollect
- : Flow<ClockEnvironment, Never, int list>)
- |> Flow.run (ClockEnvironment Clock.live);;val it: Exit<int list,Never> = Success [0; 1; 2]Schedule API reference
| Function | Signature | Behavior |
|---|---|---|
recurs |
int -> Schedule<'env, 'input, int> |
Allows exactly n additional runs and emits the zero-based recurrence index. |
spaced |
TimeSpan -> Schedule<'env, 'input, int> |
Continues with a fixed delay and emits the zero-based recurrence index. |
exponential |
TimeSpan -> Schedule<'env, 'input, TimeSpan> |
Continues with a delay that doubles after each run. |
jitteredWith |
(unit -> float) -> Schedule<'env, 'input, 'output> -> Schedule<'env, 'input, 'output> |
Adjusts each delay by a sampled factor, normally from 0.5 to 1.5. |
fixedRate |
TimeSpan -> Schedule<'env, 'input, int> |
Starts runs at start + n * period and emits the zero-based recurrence index. |
union |
Schedule<'env, 'input, 'o2> -> Schedule<'env, 'input, 'o1> -> Schedule<'env, 'input, 'o1 option * 'o2 option> |
Continues while either continues, with the shorter delay. |
intersect |
Schedule<'env, 'input, 'o2> -> Schedule<'env, 'input, 'o1> -> Schedule<'env, 'input, 'o1 * 'o2> |
Continues while both continue, with the longer delay. |
andThen |
Schedule<'env, 'input, 'o2> -> Schedule<'env, 'input, 'o1> -> Schedule<'env, 'input, Choice<'o1, 'o2>> |
Runs the first schedule until it stops, then the second from its start. |
whileInput / untilInput |
('input -> bool) -> Schedule<'env, 'input, 'output> -> Schedule<'env, 'input, 'output> |
Continues while (until) the retried error or repeated value satisfies the predicate. |
whileOutput / untilOutput |
('output -> bool) -> Schedule<'env, 'input, 'output> -> Schedule<'env, 'input, 'output> |
Continues while (until) the schedule's own output satisfies the predicate. |
recursAtMost |
int -> Schedule<'env, 'input, 'output> -> Schedule<'env, 'input, 'output> |
Stops after at most n recurrences, keeping the schedule's delays. |
upTo |
TimeSpan -> Schedule<'env, 'input, 'output> -> Schedule<'env, 'input, 'output> |
Stops once a total time budget has passed since the first run. |
resetAfter |
TimeSpan -> Schedule<'env, 'input, 'output> -> Schedule<'env, 'input, 'output> |
Restarts the count after a run that lasted at least the given time. |
elapsed |
Schedule<'env, 'input, TimeSpan> |
Recurs without waiting and emits the time since the first run. |
map |
('o1 -> 'o2) -> Schedule<'env, 'input, 'o1> -> Schedule<'env, 'input, 'o2> |
Transforms the schedule's output. |
Flow.retry |
Schedule<'env, 'error, 'output> -> Flow<'env, 'error, 'value> -> Flow<'env, 'error, 'value> |
Retries the flow after Cause.Fail. |
Flow.repeat |
Schedule<'env, 'value, 'output> -> Flow<'env, 'error, 'value> -> Flow<'env, 'error, 'value> |
Repeats the flow after success. |
FlowStream.fromSchedule |
Schedule<'env, unit, 'output> -> FlowStream<'env, 'error, 'output> |
Emits each output after its delay; ends when the schedule stops. |

