air/drain.bend source
air/drain.bend on the hub · documented module
import Base# Drain# =====## Graceful shutdown. Bend has no signals, so the app flips a `Switch`;# the server then stops accepting, lets the requests in flight finish,# and returns. The `Tally` counts those requests: one value in a one-slot# channel, where taking it is the lock and putting it back the unlock.# Switch# ------def Switch() -> Data: Chan(Unit)def Switch.new() -> IO(Chan(Unit)): Chan.new(Unit, 1)# Flips the switch. Flipping it again does nothing.def Switch.flip(switch: Chan(Unit)) -> IO(Unit): Chan.close(Unit, switch)# Returns once the switch is flipped.def Switch.wait(switch: Chan(Unit)) -> IO(Unit): IO.bind(Maybe<&1, Unit>, Unit, Chan.recv(Unit, switch), got => IO.pure(Unit, Unit{}))# Tally# -----type Tally is Data: Open{n: U32} Draining{n: U32}def Tally.count(t: Tally) -> U32: match t: case Open{n}: n case Draining{n}: n# Whether a new request may start.def Tally.admit(t: Tally) -> Bool: match t: case Open{n}: True{} case Draining{n}: False{}# A request starts; none does while draining.def Tally.enter(t: Tally) -> Tally: match t: case Open{n}: Open{U32.inc(n)} case Draining{n}: Draining{n}def Tally.leave(t: Tally) -> Tally: match t: case Open{n}: Open{U32.sub(n, 1)} case Draining{n}: Draining{U32.sub(n, 1)}def Tally.drain(t: Tally) -> Tally: match t: case Open{n}: Draining{n} case Draining{n}: Draining{n}# Drained: no request in flight, and none admitted since.def Tally.clear(t: Tally) -> Bool: match t: case Open{n}: False{} case Draining{n}: U32.is_zero(n)# Gate# ----def Gate() -> Data: Chan(Tally)def Gate.new.go(gate: Chan(Tally)) -> IO(Chan(Tally)): +g = gate IO.bind(Bool, Chan(Tally), Chan.send(Tally, g, Open{0}), ok => IO.pure(Chan(Tally), g))def Gate.new() -> IO(Chan(Tally)): IO.bind(Chan(Tally), Chan(Tally), Chan.new(Tally, 1), Gate.new.go)def swap.put(f: Tally -> Tally, +gate: Chan(Tally), t: Tally) -> IO(Maybe<&1, Tally>): +v = t IO.bind(Bool, Maybe<&1, Tally>, Chan.send(Tally, gate, f(v)), ok => IO.pure(Maybe<&1, Tally>, Some{v}))def swap.go(f: Tally -> Tally, +gate: Chan(Tally), got: Maybe<&1, Tally>) -> IO(Maybe<&1, Tally>): match got: case None{}: IO.pure(Maybe<&1, Tally>, None{}) case Some{t}: swap.put(f, gate, t)# Replaces the tally with `f` of it and answers the old one.def swap(f: Tally -> Tally, +gate: Chan(Tally)) -> IO(Maybe<&1, Tally>): IO.bind(Maybe<&1, Tally>, Maybe<&1, Tally>, Chan.recv(Tally, gate), swap.go(f, gate))def admitted(got: Maybe<&1, Tally>) -> Bool: match got: case None{}: False{} case Some{t}: Tally.admit(t)def cleared(got: Maybe<&1, Tally>) -> Bool: match got: case None{}: True{} case Some{t}: Tally.clear(t)# A request starts: True when admitted, and then `leave` must follow.def enter(+gate: Chan(Tally)) -> IO(Bool): IO.bind(Maybe<&1, Tally>, Bool, swap(Tally.enter, gate), got => IO.pure(Bool, admitted(got)))def leave(+gate: Chan(Tally)) -> IO(Unit): IO.bind(Maybe<&1, Tally>, Unit, swap(Tally.leave, gate), got => IO.pure(Unit, Unit{}))def is_open(+gate: Chan(Tally)) -> IO(Bool): IO.bind(Maybe<&1, Tally>, Bool, swap(t => t, gate), got => IO.pure(Bool, admitted(got)))# Stops admitting requests.def close(+gate: Chan(Tally)) -> IO(Unit): IO.bind(Maybe<&1, Tally>, Unit, swap(Tally.drain, gate), got => IO.pure(Unit, Unit{}))def settled(+gate: Chan(Tally)) -> IO(Bool): IO.bind(Maybe<&1, Tally>, Bool, swap(t => t, gate), got => IO.pure(Bool, cleared(got)))def in_flight(got: Maybe<&1, Tally>) -> U32: match got: case None{}: 0 case Some{t}: Tally.count(t)def count(+gate: Chan(Tally)) -> IO(U32): IO.bind(Maybe<&1, Tally>, U32, swap(t => t, gate), got => IO.pure(U32, in_flight(got)))# Polling period while draining, in ms.def poll() -> U32: 50def await(fuel: Nat, +gate: Chan(Tally), done: Bool) -> IO(Bool): match fuel done: case _ True{}: IO.pure(Bool, True{}) case 0n False{}: IO.pure(Bool, False{}) case 1n+p False{}: do IO<Bool>: IO.sleep(poll()) d : Bool <- settled(gate) await(p, gate, d)# Waits up to `grace` ms for the requests in flight to finish. True when# they did.def wait_for(+gate: Chan(Tally), +grace: U32) -> IO(Bool): IO.bind(Bool, Bool, settled(gate), await(U32.to_nat(U32.div(grace, poll())), gate))