Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-resilience

Resilience pipelines for Pudu: retry, circuit breaker, timeout, fallback, hedging, rate limiting, and chaos injection

0.1.0Apache-2.01

InstallClose

README.md

Markdown134 lines6.5 KB

GitHub ↗

pudu-lang-resilience

Pudu

Pudu | Documentation | Packages | Contributing

Resilience pipelines for Pudu. A pipeline wraps a callback in layers of policy — retry, circuit breaker, timeout, fallback, hedging, rate limiting, and chaos injection — and answers one Outcome: the callback's value, or a Failure saying why there is none. Nothing is thrown; every rejection a strategy makes is a value the caller can match on.

import PuduLangResilience as Resilience
import PuduLangResilience.Context as Context
import PuduLangResilience.Pipeline as Pipeline
import PuduLangResilience.Retry as Retry
import PuduLangResilience.Timeout as Timeout

fn main() -> Int {
  let pipeline = match Pipeline.build([
      Retry.strategy(Retry.Options{..Retry.defaults(), backoff: Retry.Exponential, useJitter: true}),
      Timeout.strategy(Timeout.after(2000))
    ]) {
    case Ok(built) => built
    case Err(invalid) => panic(Pipeline.explain(&invalid))
  }
  let price = Pipeline.run(&pipeline, fn(context: Context.Context) -> Result[Int, Str] { fetchPrice(&context) })
  if price == Ok(42) { 0 } else { 1 }
}

Installing

pudu install @chrismichaelps/pudu-lang-resilience

It needs Pudu 0.1.2 or later. Every module the package ships is PuduLangResilience or under it, so it takes no module name from the program that installs it.

Concepts

ConceptModuleWhat it is
Outcome and failurePuduLangResilienceOutcome[T, E] is Result[T, Failure[E]]. Failure is Raised(E), TimedOut, BrokenCircuit, IsolatedCircuit, RateLimited, Cancelled, or Crashed.
PipelinePipelineStrategies composed outermost first. build, buildWith, execute, run, asStrategy, describe, empty.
ContextContextThe operation key, the cancellation token, and typed properties of one execution.
PredicatePredicateWhich outcomes a reactive strategy handles: failures, raised, results, result, timeouts, brokenCircuits, rateLimited, anyOf, never.
StrategyStrategyOne layer. Strategy.custom builds your own; the Runtime lends it the pipeline's clock, randomizer, and telemetry.
RegistryRegistryNamed pipelines built once from a builder, shared, and rebuilt with reload.
TelemetryTelemetry, Telemetry.Meter, Telemetry.LogEvents with a severity and source, listeners, severity overrides, counters and durations, and one-line logs.
Time and chanceClock, RandomizerThe system clock or a manual one; the system randomizer, a seeded one, or a fixed one.

Strategies

Reactive strategies decide from the outcome; proactive ones decide before or while the callback runs. Every strategy takes an options record with defaults, and Pipeline.build reports every invalid option at once.

StrategyModuleDefaults
RetryRetry3 retries, constant 2 s delay, no jitter; Constant, Linear, or Exponential backoff, maxDelay, delayGenerator, onRetry, Retry.UNLIMITED
Circuit breakerCircuitBreakeropens at a failure ratio of 0.1 over at least 100 executions in 30 s, for 5 s; breakDurationGenerator, manualControl, stateProvider, onOpened, onClosed, onHalfOpened
TimeoutTimeout30 s; generator, onTimeout
FallbackFallbackhandles every failure except a cancellation; action is required, withValue supplies one
HedgingHedging1 hedged attempt after 2 s; a delay of 0 starts every attempt at once, Hedging.WAIT_FOR_OUTCOME starts the next only after a handled outcome; actionGenerator, delayGenerator, onHedging
Rate limiterRateLimitera concurrency limiter of 1000 permits and no queue; concurrency, using, partitioned, onRejected

Limiters stand on their own too, under Limiter: Limiter.Concurrency, Limiter.TokenBucket, Limiter.FixedWindow, Limiter.SlidingWindow, Limiter.Partitioned, and Limiter.chain. Each queues requests up to queueLimit permits, oldest or newest first, and answers a Lease that is Granted, Denied with the wait before a retry when it is known, or Abandoned when the token fired while waiting.

Chaos

Chaos strategies inject trouble into a share of executions so the rest of the pipeline can be tested against it. Each takes a Chaos.Injection: a rate from 0 to 1 (0.001 by default), an optional rate generator, and whether it is enabled, optionally per execution.

StrategyModuleInjects
FaultChaos.Faulta Failure instead of running the callback
OutcomeChaos.Outcomean Outcome instead of running the callback
LatencyChaos.Latencya delay before the callback runs (30 s by default)
BehaviorChaos.Behavioran action before the callback; its failure is answered instead

Chaos.Weighted.choose picks among several faults or outcomes by weight.

Cancellation

Cancellation is cooperative. A callback asks Context.check(&context)? between steps and waits with Context.pause(&context, millis)?; both answer Cancelled once the token fires. A timeout answers TimedOut when the callback stops because of the timeout's own token; a callback that ignores its token keeps its value. Hedging cancels the attempts that lost and waits for each of them before it answers, so no thread outlives the call.

Testing with a manual clock

Pass Clock.ofManual(&clock) and Randomizer.fixed(0.5d) or Randomizer.seeded(42) in Pipeline.Options. Retry waits, circuit breaks, window limits, and injected latency then move with Clock.advance, and Clock.sleeps(&clock) lists every wait a strategy asked for.

Examples

examples/ holds runnable programs: QuickStart, CircuitBreaking, RateLimiting, Hedging, ChaosTesting, and Registry.

pudu run examples/QuickStart.pudu

Developing

pudu test test                                        # every suite
pudu fmt --check src test tools examples && pudu lint src test tools examples
pudu run tools/Mutate.pudu --domain --threshold 100   # mutation testing of the pure layer

The design lives in the wiki vault: one page per source file, the decisions behind each strategy, and the Pudu grammar rules the code follows.

License

Apache License 2.0.