Logo Orsak

Resilience

Calls to other systems fail: networks drop packets, services restart, databases time out. Since effects are values that only run when asked to, running one again is just a matter of asking again, and Orsak comes with functions for retrying, backing off, logging failures, timing out, and keeping background work alive. The functions in this guide come from two places:

Opening both makes all of them available as Effect.*.

This page is a script: it runs with dotnet fsi docs/resilience.fsx once Orsak is built, and the output shown is from such a run. Timings vary between runs, as the backoff is randomized.

A flaky dependency

The examples use an inventory service, described by an interface in the usual way, with a provider interface and a module creating the effect:

open System
open System.Diagnostics
open System.Threading
open System.Threading.Tasks
open Microsoft.Extensions.Logging
open Orsak
open Orsak.Resilience

type StockError =
    | Unavailable
    | UnknownProduct of string
    | TimedOut

type IInventory =
    abstract GetStock: product: string -> Task<Result<int, StockError>>

type IInventoryProvider =
    abstract Inventory: IInventory

module Inventory =
    let getStock product =
        Effect.Create(fun (p: #IInventoryProvider) -> p.Inventory.GetStock product)

The implementation used here is unavailable for its first failures calls, and then answers. It prints each call, with the time since it was created, and can be slowed down to show timeouts:

type FlakyInventory(failures: int, ?latency: TimeSpan) =
    let clock = Stopwatch.StartNew()
    let mutable calls = 0

    interface IInventory with
        member _.GetStock product = task {
            calls <- calls + 1
            printfn $"%5d{clock.ElapsedMilliseconds} ms  call {calls}"

            match latency with
            | Some latency -> do! Task.Delay latency
            | None -> ()

            if product <> "orsak-mug" then return Error(UnknownProduct product)
            elif calls <= failures then return Error Unavailable
            else return Ok 42
        }

The environment provides the inventory, and implements ILoggerFactory, which Effect.logError uses. A real application would use the logger factory from its host; this one writes to the console.

type Env(inventory: IInventory) =
    let logging: ILoggerFactory = new ConsoleLoggerFactory()

    interface IInventoryProvider with
        member _.Inventory = inventory

    interface ILoggerFactory with
        member _.CreateLogger(category) = logging.CreateLogger(category)
        member _.AddProvider(provider) = logging.AddProvider(provider)
        member _.Dispose() = ()

/// Runs an effect with an inventory, and prints its result.
let runWith (inventory: IInventory) (effect: Effect<Env, 'a, 'e>) =
    let result = effect |> Effect.run (new Env(inventory))
    printfn $"          result: %A{result.AsTask().Result}"

Without any resilience, the first failure is the result:

Inventory.getStock "orsak-mug" |> runWith (FlakyInventory(failures = 2))
    1 ms  call 1
          result: Error Unavailable

Retrying

Effect.retryTimes runs the effect again when it fails, up to the given number of times after the first attempt, and fails with the last error if every attempt does:

Inventory.getStock "orsak-mug"
|> Effect.retryTimes 3L
|> runWith (FlakyInventory(failures = 2))
    2 ms  call 1
    3 ms  call 2
    3 ms  call 3
          result: Ok 42

Not every error is worth retrying: an unknown product will stay unknown. Effect.retryWhile retries for as long as the error passes a test, and fails with the first error that doesn't:

let isTransient error =
    match error with
    | Unavailable
    | TimedOut -> true
    | UnknownProduct _ -> false

Inventory.getStock "orsak-cup"
|> Effect.retryWhile isTransient
|> runWith (FlakyInventory(failures = 2))
    0 ms  call 1
          result: Error (UnknownProduct "orsak-cup")

Effect.retryWhile has no limit, so it suits errors that are known to pass. Combine it with Effect.retryTimes to also bound the attempts.

Backing off

Retrying immediately puts more load on a service that is already struggling. Effect.addDelayOnError waits before returning a failure, so that the next attempt comes later, and each consecutive failure waits longer: the delay grows exponentially from the given base delay, with random jitter, so that many clients don't retry in lockstep. A success resets it. The delays are in seconds:

Inventory.getStock "orsak-mug"
|> Effect.addDelayOnError 0.05<s>
|> Effect.retryTimes 5L
|> runWith (FlakyInventory(failures = 4))
    5 ms  call 1
   62 ms  call 2
   93 ms  call 3
  203 ms  call 4
  329 ms  call 5
          result: Ok 42

Effect.addDelayOnErrorWithMax also caps the delay. The delays use the environment's ITimeProvider, if it implements one, so that tests can run them on a fake clock, and likewise its IRandomProvider for the jitter and its ICancellationProvider to cancel a delay.

Logging failures

A retried failure is invisible from the outside, unless it is logged. Effect.logError logs the error when the effect fails, with the environment's ILoggerFactory, and fails with the same error. Placed before the retry, it logs every failed attempt:

Inventory.getStock "orsak-mug"
|> Effect.logError (fun logging error ->
    logging.CreateLogger("Inventory").LogWarning("Stock lookup failed: {Error}", error))
|> Effect.addDelayOnError 0.05<s>
|> Effect.retryTimes 5L
|> runWith (FlakyInventory(failures = 2))
    0 ms  call 1
          Warning: Inventory: Stock lookup failed: Unavailable
   76 ms  call 2
          Warning: Inventory: Stock lookup failed: Unavailable
  154 ms  call 3
          result: Ok 42

Timing out

Effect.timeout fails with the given error when the effect takes longer than allowed. The effect itself keeps running in the background, as tasks can't be stopped from the outside; pass a cancellation token to the work to stop it. A timeout is a transient error, so it combines with retrying. Here every call is too slow, so the attempts are bounded with Effect.retryTimes; Effect.retryWhile isTransient would retry forever:

Inventory.getStock "orsak-mug"
|> Effect.timeout (TimeSpan.FromMilliseconds 100.) TimedOut
|> Effect.retryTimes 2L
|> runWith (FlakyInventory(failures = 0, latency = TimeSpan.FromMilliseconds 300.))
    0 ms  call 1
  109 ms  call 2
  217 ms  call 3
          result: Error TimedOut

Background work

A background worker, such as a queue consumer, should keep running: when there is no work, it should wait before looking again, rather than spin, and when it fails, it should carry on. The forever computation expression marks an effect that isn't meant to complete, and the functions below build one from an effect that does one unit of work.

The queue, and an effect that processes one message, returning whether there was one:

type IQueue =
    abstract TryReceive: unit -> Task<string option>

type IQueueProvider =
    abstract Queue: IQueue

module Queue =
    let tryReceive () =
        Effect.Create(fun (p: #IQueueProvider) -> p.Queue.TryReceive())

let clock = Stopwatch.StartNew()

let processNext () = eff {
    match! Queue.tryReceive () with
    | Some message ->
        printfn $"%5d{clock.ElapsedMilliseconds} ms  processed {message}"
        return true
    | None ->
        printfn $"%5d{clock.ElapsedMilliseconds} ms  queue empty"
        return false
}

Effect.addDelayWithMax_ waits after the effect returns false, with the same growing delay as above, capped at the maximum, and resets it when the effect returns true: the worker checks an idle queue less and less often, and picks up speed again as soon as there is work. Effect.retryForever keeps it going when an attempt fails, and Effect.repeatUntilCancellation repeats it until the token is cancelled:

let worker (token: CancellationToken) =
    processNext ()
    |> Effect.addDelayWithMax_ 0.02<s> 0.3<s>
    |> Effect.retryForever
    |> Effect.repeatUntilCancellation token

The result is an effect of Never, a type without values: it can only end through cancellation, or an exception. The Forever pattern matches its result, without a case for success or failure:

let messages = Collections.Concurrent.ConcurrentQueue [ "order-1"; "order-2"; "order-3" ]

type QueueEnv() =
    interface IQueueProvider with
        member _.Queue =
            { new IQueue with
                member _.TryReceive() =
                    match messages.TryDequeue() with
                    | true, message -> Task.FromResult(Some message)
                    | false, _ -> Task.FromResult None
            }

let stopAfter = new CancellationTokenSource(TimeSpan.FromSeconds 1.0)

match (worker stopAfter.Token |> Effect.run (QueueEnv())).AsTask().Result with
| Forever -> printfn "          worker stopped"
   10 ms  processed order-1
   11 ms  processed order-2
   11 ms  processed order-3
   11 ms  queue empty
   32 ms  queue empty
   62 ms  queue empty
  109 ms  queue empty
  186 ms  queue empty
  422 ms  queue empty
  624 ms  queue empty
  937 ms  queue empty
          worker stopped

In an ASP.NET Core application, AddEffectWorker from Orsak.AspNetCore hosts such a worker as a background service.

namespace System
namespace System.Diagnostics
namespace System.Threading
namespace System.Threading.Tasks
namespace Microsoft
namespace Microsoft.Extensions
namespace Microsoft.Extensions.Logging
namespace Orsak
namespace Orsak.Resilience
type StockError = | Unavailable | UnknownProduct of string | TimedOut
Multiple items
val string: value: 'T -> string

--------------------
type string = String
type IInventory = abstract GetStock: product: string -> Task<Result<int,StockError>>
Multiple items
type Task = interface IAsyncResult interface IDisposable new: action: Action -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable + 1 overload member ContinueWith: continuationAction: Action<Task,obj> * state: obj -> Task + 19 overloads member Dispose: unit -> unit member GetAwaiter: unit -> TaskAwaiter member RunSynchronously: unit -> unit + 1 overload member Start: unit -> unit + 1 overload member Wait: unit -> unit + 5 overloads ...
<summary>Represents an asynchronous operation.</summary>

--------------------
type Task<'TResult> = inherit Task new: ``function`` : Func<obj,'TResult> * state: obj -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable<'TResult> + 1 overload member ContinueWith: continuationAction: Action<Task<'TResult>,obj> * state: obj -> Task + 19 overloads member GetAwaiter: unit -> TaskAwaiter<'TResult> member WaitAsync: cancellationToken: CancellationToken -> Task<'TResult> + 4 overloads member Result: 'TResult static member Factory: TaskFactory<'TResult>
<summary>Represents an asynchronous operation that can return a value.</summary>
<typeparam name="TResult">The type of the result produced by this <see cref="T:System.Threading.Tasks.Task`1" />.</typeparam>


--------------------
Task(action: Action) : Task
Task(action: Action, cancellationToken: CancellationToken) : Task
Task(action: Action, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj) : Task
Task(action: Action, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken) : Task
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task

--------------------
Task(``function`` : Func<'TResult>) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: CancellationToken) : Task<'TResult>
Task(``function`` : Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Multiple items
module Result from Microsoft.FSharp.Core

--------------------
type Result<'T,'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
type IInventoryProvider = abstract Inventory: IInventory
val getStock: product: string -> Effect<#IInventoryProvider,int,StockError>
val product: string
Multiple items
union case Effect.Effect: EffectDelegate<'r,'a,'e> -> Effect<'r,'a,'e>

--------------------
module Effect from Orsak.Resilience
<summary> Resilience functions for effects: retrying with backoff, logging errors, and running effects perpetually. </summary>

--------------------
module Effect from Orsak
<summary> Functions for running, combining, recovering and repeating effects. </summary>

--------------------
type Effect = static member Create: f: ('a -> Task<Result<'b,'e>>) -> Effect<'a,'b,'e> + 2 overloads static member Error: error: 'e -> Effect<'a,'b,'e>
<summary> Creates effects. <c>Effect.Create</c> turns a function of an environment into an effect, and is how effects are made from the interfaces that describe side effects. The function may return a plain value, a <c>Result</c>, a <c>Task</c>, a <c>ValueTask</c> or an <c>Async</c>, of a value or of a <c>Result</c>; returning a <c>Result</c> lets the effect fail. </summary>
<example> The usual pattern: an interface describing the side effect, a provider interface exposing it, and a function creating the effect, which requires any environment that implements the provider (<c>#IConsoleProvider</c>). <code lang="fsharp"> type IConsole = abstract ReadLine: unit -&gt; string abstract WriteLine: string -&gt; unit type IConsoleProvider = abstract Console: IConsole module Console = let readLine () = Effect.Create(fun (p: #IConsoleProvider) -&gt; p.Console.ReadLine()) let writeLine line = Effect.Create(fun (p: #IConsoleProvider) -&gt; p.Console.WriteLine line) </code> The Orsak.Myriad generators can write the functions of such a module from the interface. </example>


--------------------
type Effect<'r,'a,'e> = | Effect of EffectDelegate<'r,'a,'e> member Run: r: 'r -> AsyncResult<'a,'e> member RunOrFail: r: 'r -> Task<'a> static member (<*>)<'b,'a (requires member (+))> : f: Effect<'r,('b -> 'a),'a0> * e: Effect<'r,'b,'a0> -> Effect<'r,'a,'a0> (requires member (+)) static member (>>=) : h: Effect<'r,'a,'e> * f: ('a -> Effect<'r,'b,'e>) -> Effect<'r,'b,'e> static member Map: h: Effect<'r,'a,'e> * f: ('a -> 'b) -> Effect<'r,'b,'e> static member Return<'a,'b,'c> : a: 'a0 -> Effect<'b,'a0,'c> static member ap<'r,'a,'b,'e> : applicative: Effect<'r,('b -> 'a),'e> -> e: Effect<'r,'b,'e> -> Effect<'r,'a,'e>
<summary> Describes an effect that can either succeed with <typeparamref name="'a" />, or fail with <typeparamref name="'e" />. The effect is 'cold', and only starts when run with an <typeparamref name="'r" />. </summary>
<remarks> Effects are usually written with the <c>eff</c> computation expression, and created from interfaces that describe side effects with <c>Effect.Create</c>. The environment <typeparamref name="'r" /> is inferred from what the effect uses, and supplied when the effect is run. </remarks>
<example><code lang="fsharp"> let greet () = eff { let! name = Console.readLine () do! Console.writeLine $"Hello {name}" } // at the composition root, with an environment providing the console effects: let! result = greet () |&gt; Effect.run env </code></example>
<typeparam name="'r"> The environment required to run the effect </typeparam>
<typeparam name="'a"> The resulting type when the effect runs successfully </typeparam>
<typeparam name="'e"> The resulting type when the effect fails</typeparam>
static member Effect.Create: f: ('a -> 'b) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Async<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> Result<'b,'e>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Task<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> ValueTask<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> Async<Result<'b,'e>>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> ValueTask<Result<'b,'e>>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Task<Result<'b,'e>>) -> Effect<'a,'b,'e>
val p: #IInventoryProvider
property IInventoryProvider.Inventory: IInventory with get
abstract IInventory.GetStock: product: string -> Task<Result<int,StockError>>
Multiple items
type FlakyInventory = interface IInventory new: failures: int * ?latency: TimeSpan -> FlakyInventory

--------------------
new: failures: int * ?latency: TimeSpan -> FlakyInventory
val failures: int
val latency: TimeSpan option
Multiple items
type TimeSpan = new: hours: int * minutes: int * seconds: int -> unit + 4 overloads member Add: ts: TimeSpan -> TimeSpan member CompareTo: value: obj -> int + 1 overload member Divide: divisor: float -> TimeSpan + 1 overload member Duration: unit -> TimeSpan member Equals: value: obj -> bool + 2 overloads member GetHashCode: unit -> int member Multiply: factor: float -> TimeSpan member Negate: unit -> TimeSpan member Subtract: ts: TimeSpan -> TimeSpan ...
<summary>Represents a time interval.</summary>

--------------------
TimeSpan ()
TimeSpan(ticks: int64) : TimeSpan
TimeSpan(hours: int, minutes: int, seconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int) : TimeSpan
val clock: Stopwatch
Multiple items
type Stopwatch = new: unit -> unit member Reset: unit -> unit member Restart: unit -> unit member Start: unit -> unit member Stop: unit -> unit member ToString: unit -> string static member GetElapsedTime: startingTimestamp: int64 -> TimeSpan + 1 overload static member GetTimestamp: unit -> int64 static member StartNew: unit -> Stopwatch static val Frequency: int64 ...
<summary>Provides a set of methods and properties that you can use to accurately measure elapsed time.</summary>

--------------------
Stopwatch() : Stopwatch
Stopwatch.StartNew() : Stopwatch
val mutable calls: int
val task: TaskBuilder
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
property Stopwatch.ElapsedMilliseconds: int64 with get
<summary>Gets the total elapsed time measured by the current instance, in milliseconds.</summary>
<returns>A read-only long integer representing the total number of milliseconds measured by the current instance.</returns>
union case Option.Some: Value: 'T -> Option<'T>
val latency: TimeSpan
Task.Delay(delay: TimeSpan) : Task
Task.Delay(millisecondsDelay: int) : Task
Task.Delay(delay: TimeSpan, timeProvider: TimeProvider) : Task
Task.Delay(delay: TimeSpan, cancellationToken: CancellationToken) : Task
Task.Delay(millisecondsDelay: int, cancellationToken: CancellationToken) : Task
Task.Delay(delay: TimeSpan, timeProvider: TimeProvider, cancellationToken: CancellationToken) : Task
union case Option.None: Option<'T>
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
union case StockError.UnknownProduct: string -> StockError
union case StockError.Unavailable: StockError
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
Multiple items
type ConsoleLoggerFactory = interface ILoggerFactory new: unit -> ConsoleLoggerFactory
 A console logger that writes synchronously, so its lines appear in order with the rest of the output.

--------------------
new: unit -> ConsoleLoggerFactory
type ILoggerFactory = inherit IDisposable override AddProvider: provider: ILoggerProvider -> unit override CreateLogger: categoryName: string -> ILogger
<summary> Represents a type used to configure the logging system and create instances of <see cref="T:Microsoft.Extensions.Logging.ILogger" /> from the registered <see cref="T:Microsoft.Extensions.Logging.ILoggerProvider" />s. </summary>
val category: string
Multiple items
type ILogger = override BeginScope<'TState> : state: 'TState -> IDisposable override IsEnabled: logLevel: LogLevel -> bool override Log<'TState> : logLevel: LogLevel * eventId: EventId * state: 'TState * ``exception`` : exn * formatter: Func<'TState,exn,string> -> unit
<summary> Represents a type used to perform logging. </summary>
<remarks>Aggregates most logging patterns to a single method.</remarks>


--------------------
type ILogger<'TCategoryName> = inherit ILogger
<summary> A generic interface for logging where the category name is derived from the specified <typeparamref name="TCategoryName" /> type name. Generally used to enable activation of a named <see cref="T:Microsoft.Extensions.Logging.ILogger" /> from dependency injection. </summary>
<typeparam name="TCategoryName">The type whose name is used for the logger category name.</typeparam>
type IDisposable = override Dispose: unit -> unit
<summary>Provides a mechanism for releasing unmanaged resources.</summary>
val level: LogLevel
val state: 'a
val ex: exn
val format: Func<'a,exn,string>
Multiple items
type Env = interface ILoggerFactory interface IInventoryProvider new: inventory: IInventory -> Env

--------------------
new: inventory: IInventory -> Env
val inventory: IInventory
val logging: ILoggerFactory
module Inventory from Resilience
(extension) ILoggerFactory.CreateLogger<'T>() : ILogger<'T>
(extension) ILoggerFactory.CreateLogger(``type`` : Type) : ILogger
ILoggerFactory.CreateLogger(categoryName: string) : ILogger
val provider: ILoggerProvider
ILoggerFactory.AddProvider(provider: ILoggerProvider) : unit
val runWith: inventory: IInventory -> effect: Effect<Env,'a,'e> -> unit
 Runs an effect with an inventory, and prints its result.
val effect: Effect<Env,'a,'e>
'a
'e
val result: AsyncResult<'a,'e>
val run: env: 'r -> e: Effect<'r,'a,'e> -> AsyncResult<'a,'e>
<summary> Starts the effect. </summary>
<typeparam name="'r"> The environment required to run the effect </typeparam>
<typeparam name="'a"> The resulting type when the effect runs successfully </typeparam>
<typeparam name="'e"> The resulting type when the effect fails</typeparam>
<param name="env">The environment needed to start the effect</param>
<param name="e">The effect to run</param>
<example><code lang="fsharp"> task { match! placeOrder order |&gt; Effect.run env with | Ok id -&gt; printfn $"Placed {id}" | Error err -&gt; printfn $"Failed: {err}" } </code></example>
ValueTask.AsTask() : Task<Result<'a,'e>>
Multiple items
val retryTimes: times: int64 -> effect: Effect<'a,'a0,'b> -> Effect<'a,'a0,'b>
<summary> If the provided effect returns an error, tries to re-run it up to <paramref name="times" /> times. </summary>
<param name="times"></param>
<param name="effect"></param>


--------------------
val retryTimes: times: int -> e: Effect<'r,'a,'e> -> Effect<'r,'a,'e>
<summary> Runs the effect again when it fails, up to <paramref name="times" /> times after the first attempt. It fails with the last error if every attempt fails. </summary>
<param name="times">How many times to retry</param>
<param name="e">The effect</param>
<example><code lang="fsharp"> sendEmail message |&gt; Effect.retryTimes 3 </code></example>
val isTransient: error: StockError -> bool
val error: StockError
union case StockError.TimedOut: StockError
val retryWhile: cond: ('e -> bool) -> e: Effect<'r,'a,'e> -> Effect<'r,'a,'e>
<summary> Runs the effect again every time it fails with an error for which <paramref name="cond" /> is true. It fails with the first error for which <paramref name="cond" /> is false. </summary>
<param name="cond">Whether to retry after a given error</param>
<param name="e">The effect</param>
<example><code lang="fsharp"> fetchPrices () |&gt; Effect.retryWhile (fun err -&gt; err = Timeout) </code></example>
val addDelayOnError: baseDelay: float<s> -> effect: Effect<'a,'a0,'e> -> Effect<'a,'a0,'e>
<summary> Takes a base delay and an effect, and returns an effect with a delay added at the end, if the provided effect returned with an error. If the effect is re-run, the delay is increased each time the original effect returns an error. If the effect returns Ok, no delay is added, and the calculated delay is reset to 0. </summary>
<param name="baseDelay"></param>
<param name="effect"></param>
type s = Data.UnitSystems.SI.UnitSymbols.s
<summary>Seconds, the unit of the delays taken by the functions in <c>Orsak.Resilience</c>.</summary>
val logError: log: (ILoggerFactory -> 'e -> unit) -> effect: Effect<'a,'a0,'e> -> Effect<'a,'a0,'e> (requires 'a :> ILoggerFactory)
<summary> Logs the error when the effect fails, with <paramref name="log" />, and then fails with the same error. The environment has to be an <see cref="T:Microsoft.Extensions.Logging.ILoggerFactory" />. </summary>
<param name="log">Logs an error, given the environment's logger factory</param>
<param name="effect">The effect</param>
<example><code lang="fsharp"> importPrices () |&gt; Effect.logError (fun factory err -&gt; factory.CreateLogger("Prices").LogError("Import failed: {Error}", err)) </code></example>
val timeout: ts: TimeSpan -> onTimeout: 'a -> eff: Effect<'b,'c,'a> -> Effect<'b,'c,'a>
<summary> Configures an effect to fail after a given amount of time, unless it has already succeeded. The original effect still executes. </summary>
<param name="ts">How long to wait for the effect</param>
<param name="onTimeout">The error to fail with when the effect doesn't finish in time</param>
<param name="eff">The effect</param>
<example><code lang="fsharp"> lookupAddress postcode |&gt; Effect.timeout (TimeSpan.FromSeconds 2.0) AddressLookupTimedOut </code></example>
TimeSpan.FromMilliseconds(milliseconds: int64) : TimeSpan
TimeSpan.FromMilliseconds(value: float) : TimeSpan
TimeSpan.FromMilliseconds(milliseconds: int64, microseconds: int64) : TimeSpan
type IQueue = abstract TryReceive: unit -> Task<string option>
type unit = Unit
type 'T option = Option<'T>
type IQueueProvider = abstract Queue: IQueue
val tryReceive: unit -> Effect<#IQueueProvider,string option,'b>
val p: #IQueueProvider
property IQueueProvider.Queue: IQueue with get
abstract IQueue.TryReceive: unit -> Task<string option>
val processNext: unit -> Effect<#IQueueProvider,bool,'b>
val eff: EffBuilder
<summary> The computation expression for writing effects. Binding (<c>let!</c>, <c>do!</c>, <c>return!</c>) works with other effects, and with <c>Task</c>, <c>ValueTask</c>, <c>Async</c> and <c>Result</c>, so .NET APIs can be used directly. It supports <c>for</c>, <c>while</c>, <c>use</c>, <c>try/with</c> and <c>try/finally</c>, and <c>and!</c> runs effects concurrently. A failed effect stops the rest of the expression, with its error. </summary>
<example><code lang="fsharp"> let placeOrder (order: Order) = eff { let! id = GuidGenerator.genGuid () let! now = Time.utcNow () do! Orders.save { order with Id = id; Placed = now } let! customer = Customers.load order.CustomerId and! stock = Stock.reserve order.Lines return id } </code></example>
module Queue from Resilience
val message: string
val worker: token: CancellationToken -> Effect<#IQueueProvider,Never,Never>
val token: CancellationToken
Multiple items
type CancellationToken = new: canceled: bool -> unit member Equals: other: obj -> bool + 1 overload member GetHashCode: unit -> int member Register: callback: Action -> CancellationTokenRegistration + 4 overloads member ThrowIfCancellationRequested: unit -> unit member UnsafeRegister: callback: Action<obj,CancellationToken> * state: obj -> CancellationTokenRegistration + 1 overload static member (<>) : left: CancellationToken * right: CancellationToken -> bool static member (=) : left: CancellationToken * right: CancellationToken -> bool member CanBeCanceled: bool member IsCancellationRequested: bool ...
<summary>Propagates notification that operations should be canceled.</summary>

--------------------
CancellationToken ()
CancellationToken(canceled: bool) : CancellationToken
val addDelayWithMax_: baseDelay: float<s> -> maxDelay: float<s> -> effect: Effect<'a,bool,'b> -> Effect<'a,unit,'b>
<summary> Takes a base delay and an effect, and returns an effect with a delay added at the end, if the provided effect returned false. If the effect is re-run, the delay is increased each time the original effect returns false. If the effect returns true, no delay is added, and the calculated delay is reset to 0. The delay till will not be longer than <paramref name="maxDelay" /> Then the bool is discarded and unit is returned. </summary>
<param name="baseDelay"></param>
<param name="maxDelay"></param>
<param name="effect"></param>
val retryForever: effect: Effect<'a,'a0,'b> -> Effect<'a,'a0,Never>
<summary> Perpetually returns the provided effect, in case of error </summary>
<param name="effect"></param>
Multiple items
val repeatUntilCancellation: token: CancellationToken -> effect: Effect<'a,unit,'b> -> Effect<'a,Never,'b>
<summary> Re-runs the provided effect until the provided token signals cancellation. If the provided effect fails, it returns with an error. </summary>
<param name="token"></param>
<param name="effect"></param>


--------------------
val repeatUntilCancellation: token: CancellationToken -> e: Effect<'a,unit,'b> -> Effect<'a,unit,'b>
<summary> Runs the effect repeatedly until <paramref name="token" /> is cancelled, which is checked before every run. It stops at the first failure. </summary>
<param name="token">The token that stops the repetition</param>
<param name="e">The effect</param>
<example><code lang="fsharp"> processNextMessage () |&gt; Effect.repeatUntilCancellation stoppingToken </code></example>
val messages: Collections.Concurrent.ConcurrentQueue<string>
Multiple items
namespace System.Collections

--------------------
namespace Microsoft.FSharp.Collections
namespace System.Collections.Concurrent
Multiple items
type ConcurrentQueue<'T> = interface IProducerConsumerCollection<'T> interface seq<'T> interface IEnumerable interface ICollection interface IReadOnlyCollection<'T> new: unit -> unit + 1 overload member Clear: unit -> unit member CopyTo: array: 'T array * index: int -> unit member Enqueue: item: 'T -> unit member GetEnumerator: unit -> IEnumerator<'T> ...
<summary>Represents a thread-safe first in-first out (FIFO) collection.</summary>
<typeparam name="T">The type of the elements contained in the queue.</typeparam>


--------------------
Collections.Concurrent.ConcurrentQueue() : Collections.Concurrent.ConcurrentQueue<'T>
Collections.Concurrent.ConcurrentQueue(collection: 'T seq) : Collections.Concurrent.ConcurrentQueue<'T>
Multiple items
type QueueEnv = interface IQueueProvider new: unit -> QueueEnv

--------------------
new: unit -> QueueEnv
Collections.Concurrent.ConcurrentQueue.TryDequeue( result: byref<string>) : bool
Task.FromResult<'TResult>(result: 'TResult) : Task<'TResult>
val stopAfter: CancellationTokenSource
Multiple items
type CancellationTokenSource = interface IDisposable new: unit -> unit + 3 overloads member Cancel: unit -> unit + 1 overload member CancelAfter: millisecondsDelay: int -> unit + 1 overload member CancelAsync: unit -> Task member Dispose: unit -> unit member TryReset: unit -> bool static member CreateLinkedTokenSource: token: CancellationToken -> CancellationTokenSource + 3 overloads member IsCancellationRequested: bool member Token: CancellationToken
<summary>Signals to a <see cref="T:System.Threading.CancellationToken" /> that it should be canceled.</summary>

--------------------
CancellationTokenSource() : CancellationTokenSource
CancellationTokenSource(millisecondsDelay: int) : CancellationTokenSource
CancellationTokenSource(delay: TimeSpan) : CancellationTokenSource
CancellationTokenSource(delay: TimeSpan, timeProvider: TimeProvider) : CancellationTokenSource
TimeSpan.FromSeconds(seconds: int64) : TimeSpan
TimeSpan.FromSeconds(value: float) : TimeSpan
TimeSpan.FromSeconds(seconds: int64, ?milliseconds: int64, ?microseconds: int64) : TimeSpan
property CancellationTokenSource.Token: CancellationToken with get
<summary>Gets the <see cref="T:System.Threading.CancellationToken" /> associated with this <see cref="T:System.Threading.CancellationTokenSource" />.</summary>
<exception cref="T:System.ObjectDisposedException">The token source has been disposed.</exception>
<returns>The <see cref="T:System.Threading.CancellationToken" /> associated with this <see cref="T:System.Threading.CancellationTokenSource" />.</returns>
active recognizer Forever: Result<Never,Never> -> unit
<summary> Active pattern for an effect that should run as long as the application is running. </summary>
<param name="_result">The result, which can only be reached if the effect ended through an exception</param>

Type something to start searching.