
Limiter.pudu
Pudu103 lines4.0 KB
1/** @Resilience.Limiter.Seam — permits leased by rate and concurrency limiters */2module PuduLangResilience.Limiter34import Std.Concurrent.Cancel as Cancel5import PuduLangResilience.Utils.Shared as Shared67/** @Resilience.Limiter.QueueOrder — which queued request is served first */8export type QueueOrder = OldestFirst | NewestFirst910/** @Resilience.Limiter.Grant — whether a request was given its permits */11export type Grant = Granted | Denied(Option[Int]) | Abandoned(Str)1213/** @Resilience.Limiter.Lease — the answer to one request and how to hand its permits back */14export type Lease = { grant: Grant, release: fn() -> () }1516/** @Resilience.Limiter.Statistics — a limiter's counts at one moment */17export type Statistics = { available: Int, queued: Int, failed: Int, succeeded: Int }1819/** @Resilience.Limiter.Limiter — leases permits, waiting or at once */20export type Limiter = {21 acquire: fn(Int, Cancel.Token) -> Lease,22 attempt: fn(Int) -> Lease,23 statistics: fn() -> Statistics,24 replenish: fn() -> Bool25}26272829export fn acquire(limiter: &Limiter, permits: Int, token: &Cancel.Token) -> Lease { (limiter.acquire)(permits, *token) }303132export fn attempt(limiter: &Limiter, permits: Int) -> Lease { (limiter.attempt)(permits) }333435export fn statistics(limiter: &Limiter) -> Statistics { (limiter.statistics)() }363738export fn replenish(limiter: &Limiter) -> Bool { (limiter.replenish)() }394041export fn isGranted(lease: &Lease) -> Bool { lease.grant == Granted }424344export fn release(lease: &Lease) -> () { (lease.release)() }454647export fn refused(grant: Grant) -> Lease { Lease{grant: grant, release: fn() -> () { () }} }484950export fn granted(handBack: fn() -> ()) -> Lease {51 let spent = Shared.shared(false)52 Lease{grant: Granted, release: fn() -> () {53 let first = Shared.change(&spent, fn(held: Bool) -> (Bool, Bool) { (true, !held) })54 if first { handBack() }55 } }56}57585960export fn chain(limiters: Array[Limiter]) -> Limiter {61 Limiter {62 acquire: fn(permits: Int, token: Cancel.Token) -> Lease { chained(&limiters, fn(limiter: Limiter) -> Lease { acquire(&limiter, permits, &token) }) },63 attempt: fn(permits: Int) -> Lease { chained(&limiters, fn(limiter: Limiter) -> Lease { attempt(&limiter, permits) }) },64 statistics: fn() -> Statistics { combined(&limiters) },65 replenish: fn() -> Bool {66 var any = false67 for limiter in limiters {68 if replenish(&limiter) { any = true }69 }70 any71 }72 }73}747576fn chained(limiters: &Array[Limiter], take: fn(Limiter) -> Lease) -> Lease {77 var held: Array[Lease] = []78 for limiter in limiters {79 let lease = take(limiter)80 if !isGranted(&lease) {81 for taken in held { release(&taken) }82 return lease83 }84 held = held.push(lease)85 }86 granted(fn() -> () {87 for taken in held { release(&taken) }88 })89}909192fn combined(limiters: &Array[Limiter]) -> Statistics {93 var total = Statistics{available: 0, queued: 0, failed: 0, succeeded: 0}94 var first = true95 for limiter in limiters {96 let counts = statistics(&limiter)97 let available = if first || counts.available < total.available { counts.available } else { total.available }98 total = Statistics{available: available, queued: total.queued + counts.queued, failed: total.failed + counts.failed, succeeded: total.succeeded + counts.succeeded}99 first = false100 }101 total102}103