Chaos engineering
Kevlar.Chaos is an optional package for deliberately injecting latency, exceptions, typed
results, and caller-defined behavior through ordinary shields.
dotnet add package Kevlar.Chaos
Every chaos strategy is disabled by default. Enabling one should be an explicit, reviewable decision. Bound its blast radius with a low rate plus an operation, environment, predicate, or dynamic kill switch. Do not broadly enable chaos in production; start in tests or staging, keep an immediate disable path, monitor injection metrics, and expand gradually.
Inject latency and faults
using Kevlar;
using Kevlar.Chaos;
var latency = ChaosShield.Latency(options =>
{
options.Enabled = true;
options.Delay = TimeSpan.FromMilliseconds(250);
options.InjectionRate = 0.02;
options.Environment = "staging";
});
var faults = ChaosShield.Fault(options =>
{
options.Enabled = true;
options.ExceptionGenerator = context =>
new(new IOException($"Injected fault in {context.ShieldName}"));
options.Operation = "checkout";
options.InjectionRate = 0.01;
});
using (ChaosScope.Begin(operation: "checkout", environment: "staging"))
{
var result = await Shield.Compose(latency, faults)
.WithName("payments")
.ExecuteAsync(token => LoadUserAsync(id, token), cancellationToken);
}
Latency honors the shield's TimeProvider and caller cancellation token. Fault injection
short-circuits the pipeline with the exact configured exception. If no exception is configured,
ChaosInjectedException is used.
Inject typed outcomes
Typed outcome injection is useful for failures represented as values, such as HTTP responses or
sentinels. It returns a Shield<TResult>, so normal typed handling and composition remain available:
using Kevlar.Chaos;
var unavailable = ChaosShield.Outcome<int>(options =>
{
options.Enabled = true;
options.Result = -1;
options.InjectionRate = 0.05;
});
var shield = Shield.For<int>()
.WhenResultEquals(-1)
.FallbackTo(0)
.Wrap(unavailable);
var value = await shield.ExecuteAsync(static _ => new ValueTask<int>(42));
Dynamic control and deterministic runs
Enabled is the primary safety gate. EnabledGenerator can add a live kill switch;
InjectionRateGenerator and Predicate receive the current KevlarContext. Fixed configuration
is copied when the shield is built, while generators are evaluated on every execution. Every
generator returns ValueTask<T> and is awaited. Return new(value) for synchronous work; a
generator that actually yields requires ExecuteAsync, and the async-configuration analyzer
reports it when used with synchronous Execute.
using Kevlar.Chaos;
var chaosEnabled = true;
var deterministic = ChaosShield.Fault(options =>
{
options.Enabled = true;
options.EnabledGenerator = _ => new(chaosEnabled);
options.Predicate = context => !context.IsSynchronous;
options.InjectionRateGenerator = context =>
new(context.ShieldName == "test-run" ? 0.25 : 0);
options.Seed = 42;
}).WithName("test-run");
A seed makes the random sample sequence reproducible. One shield instance is thread-safe and can be shared, but concurrent callers can consume that deterministic sequence in different orders; use separate seeded shields when each scenario needs its own exact sequence.
Custom behavior
ChaosShield.Behavior awaits caller-supplied behavior before continuing. It can model effects
that are not just a delay or result, and any exception it throws is preserved as the execution
failure.
using Kevlar.Chaos;
var behavior = ChaosShield.Behavior(options =>
{
options.Enabled = true;
options.Behavior = context =>
{
Console.WriteLine($"Injecting custom behavior into {context.ShieldName}");
return ValueTask.CompletedTask;
};
});
Observe injections
OnInjected receives a ChaosEvent immediately before an injection and returns ValueTask; it is
awaited before the injection proceeds, and return default; suffices for synchronous work. The
event identifies the injection kind, effective rate and sample, operation, environment, and
KevlarContext. The context is pooled: copy values inside the callback rather than retaining the
context or event. Hook exceptions follow the shared
callback-failure contract: Kevlar reports them without
replacing the injected outcome.
On .NET 8+, the Kevlar.Chaos meter publishes the kevlar.chaos.injections counter. Its tags are
kevlar.chaos.kind plus the available kevlar.shield.name, kevlar.chaos.operation, and
kevlar.chaos.environment values. Subscribe with OpenTelemetry using
AddMeter(ChaosDiagnostics.MeterName).
For latency tests, attach a FakeTimeProvider with WithTimeProvider and advance virtual time;
no real waiting is required.