Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Polly-Contrib/Simmy/llms.txt

Use this file to discover all available pages before exploring further.

Simmy ships async variants of every chaos policy. The async forms integrate naturally with async/await application code, support cooperative cancellation throughout, and return awaitable policy instances that slot into Polly’s PolicyWrap alongside other async resilience policies. The factory API mirrors the synchronous one exactly — you use the same fluent builder options — but the underlying delegates return Task<T> rather than T, and cancellation tokens are propagated through every layer.

Factory Methods

All async monkey policies are created through static methods on MonkeyPolicy. Each method accepts a configuration callback that receives a typed async-options object.
// Builds an AsyncInjectOutcomePolicy that throws the configured exception.
var chaosPolicy = MonkeyPolicy.InjectExceptionAsync(
    Action<InjectOutcomeAsyncOptions<Exception>> configureOptions
);
Exactly three options are required on every async policy: the chaos payload (e.g. Fault, Latency, Behaviour, Result), InjectionRate, and Enabled (or EnabledWhen). Omitting any of them causes the factory method to throw ArgumentNullException.

How Async Delegates Differ from Sync

The core difference between the sync and async options types is that all Func<> delegates on the async variants return Task<T> instead of T. This means:
  • The enabled check is Func<Context, CancellationToken, Task<bool>> instead of Func<Context, CancellationToken, bool>.
  • The injection-rate provider is Func<Context, CancellationToken, Task<double>> instead of Func<Context, CancellationToken, double>.
  • Outcome, latency, and behaviour delegates all return awaitables.
This lets you perform genuine async I/O — for example, reading a chaos flag from a remote configuration service — inside the delegate without blocking a thread.
// Sync: delegate returns bool
.EnabledWhen((ctx, ct) =>
{
    return configService.IsChaosEnabled(ctx.OperationKey);
})
// Sync: delegate returns double
.InjectionRate((ctx, ct) =>
{
    return configService.GetInjectionRate(ctx.OperationKey);
})

InjectExceptionAsync

Throws a configured exception before the wrapped call executes. Useful for simulating downstream service failures in async call chains.
var chaosPolicy = MonkeyPolicy.InjectExceptionAsync(with =>
    with.Fault(new HttpRequestException("Injected HTTP failure"))
        .InjectionRate(0.05)
        .Enabled()
);

// Execute with cancellation support
await chaosPolicy.ExecuteAsync(
    async ct => await httpClient.GetAsync("/api/resource", ct),
    cancellationToken
);
For dynamic faults, use a Func<Context, CancellationToken, Task<Exception>>:
var chaosPolicy = MonkeyPolicy.InjectExceptionAsync(with =>
    with.Fault(async (ctx, ct) =>
        {
            var msg = await diagnosticsService.GetFaultMessageAsync(ctx.OperationKey, ct);
            return new ApplicationException(msg);
        })
        .InjectionRate(0.1)
        .EnabledWhen(async (ctx, ct) =>
            await featureFlags.IsEnabledAsync("ChaosEnabled", ct))
);

InjectResultAsync

Substitutes a fake result without executing the inner call. Ideal for simulating error responses from HTTP APIs or databases in async pipelines.
var badResponse = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);

var chaosPolicy = MonkeyPolicy.InjectResultAsync<HttpResponseMessage>(with =>
    with.Result(badResponse)
        .InjectionRate(0.05)
        .Enabled()
);
With a Func<Context, CancellationToken, Task<TResult>>:
var chaosPolicy = MonkeyPolicy.InjectResultAsync<HttpResponseMessage>(with =>
    with.Result(async (ctx, ct) =>
        {
            var code = await configService.GetHttpStatusCodeAsync(ctx.OperationKey, ct);
            return new HttpResponseMessage(code);
        })
        .InjectionRate(0.1)
        .Enabled()
);

InjectLatencyAsync

Delays execution by the configured TimeSpan before the wrapped call runs. Since v0.2.0, the injected delay is cancellable: if the CancellationToken is signalled during the delay, the delay ends and the cancellation propagates normally.
var chaosLatencyPolicy = MonkeyPolicy.InjectLatencyAsync(with =>
    with.Latency(TimeSpan.FromSeconds(5))
        .InjectionRate(0.1)
        .Enabled()
);
With a dynamic latency delegate:
var chaosLatencyPolicy = MonkeyPolicy.InjectLatencyAsync(with =>
    with.Latency(async (ctx, ct) =>
        {
            int ms = await configService.GetLatencyMsAsync(ctx.OperationKey, ct);
            return TimeSpan.FromMilliseconds(ms);
        })
        .InjectionRate(0.1)
        .EnabledWhen(async (ctx, ct) =>
            await configService.IsChaosEnabledAsync(ctx.OperationKey, ct))
);
Because latency injection uses Task.Delay internally, the injected pause participates in cooperative cancellation. A timeout policy wrapping the chaos policy will cancel the delay exactly as it would cancel a real slow downstream call.

InjectBehaviourAsync

Executes a Func<Task> (or Func<Context, CancellationToken, Task>) before the wrapped call. Use this to simulate side effects such as cache eviction, VM restarts, or sending notifications.
// Simple Func<Task> — no context access needed
var chaosPolicy = MonkeyPolicy.InjectBehaviourAsync(with =>
    with.Behaviour(async () =>
        {
            await notificationService.SendChaosAlertAsync();
        })
        .InjectionRate(0.01)
        .Enabled()
);

// Func<Context, CancellationToken, Task> — full context access
var chaosPolicy = MonkeyPolicy.InjectBehaviourAsync(with =>
    with.Behaviour(async (ctx, ct) =>
        {
            await infrastructureService.RestartNodeAsync(ctx.OperationKey, ct);
        })
        .InjectionRate(0.01)
        .EnabledWhen(async (ctx, ct) =>
            await configService.IsChaosEnabledAsync(ctx.OperationKey, ct))
);

Executing Async Policies

Call ExecuteAsync to run code through an async monkey policy, passing a cancellation token:
var result = await chaosPolicy.ExecuteAsync(
    async ct => await service.GetDataAsync(parameters, ct),
    myCancellationToken
);
The CancellationToken is forwarded to:
  • The EnabledWhen delegate
  • The InjectionRate delegate
  • The chaos payload delegate (fault factory, latency factory, behaviour)
  • The user-supplied inner delegate
This means all async delegates receive the same cancellation signal so that cancellation is consistent throughout.

Complete Async PolicyWrap Example

The recommended way to use async Simmy policies in production is inside an async PolicyWrap, with the chaos policy as the innermost policy. For a detailed explanation of why placement matters, see Policy Wrap.
// 1. Build outer resilience policies
var fallbackPolicy = Policy<HttpResponseMessage>
    .Handle<Exception>()
    .FallbackAsync(new HttpResponseMessage(HttpStatusCode.OK));

var timeoutPolicy = Policy
    .TimeoutAsync(TimeSpan.FromSeconds(10));

// 2. Build the innermost async chaos policy
var chaosLatencyPolicy = MonkeyPolicy.InjectLatencyAsync(with =>
    with.Latency(TimeSpan.FromSeconds(5))
        .InjectionRate(0.1)
        .Enabled()
);

// 3. Compose into a PolicyWrap (chaos is innermost)
var policyWrap = Policy
    .WrapAsync(fallbackPolicy, timeoutPolicy, chaosLatencyPolicy);

// 4. Execute through the wrap
var result = await policyWrap.ExecuteAsync(
    token => service.GetFoo(parametersBar, token),
    myCancellationToken
);

Build docs developers (and LLMs) love