ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.InFlight

Description

Async-safe release for a claimed in-flight slot.

Several places in the proxy collapse duplicate concurrent work onto a single execution: the metadata cache fronts one upstream fetch per (source, package) (Ecluse.Core.Server.Cache), and the credential refresher mints at most one token at a time (Ecluse.Core.Credential.Refresh). Each does it by atomically __claiming a slot__ -- installing an in-flight marker, or setting a flag -- so a second caller finds the claim and waits (or serves a still-valid value) rather than launching its own run.

A claimed slot carries a sharp obligation: once claimed it must be __released on every exit__, or the slot wedges. A naive claim; run; free leaks the slot if the claiming thread is hit by an asynchronous exception -- a request timeout, a killed handler thread -- in the window between the claim and the run that frees it: a follower waiting on the slot parks forever, and a later caller blocked behind it never proceeds, until the process restarts. That is one shared hazard, found and fixed independently in both consumers, which is why the release discipline lives here once.

guardInFlight is that discipline. The caller claims its slot in a single masked STM transaction and then, with no interruptible step in between, hands the leader's run to guardInFlight. It runs the body and guarantees the slot is released on __every exit__ -- normal completion, a synchronous failure, or an asynchronous exception anywhere from the claim onward, including the claim → runner handoff -- and that any follower waiting on the slot's result is handed the orphaning error rather than left to park. The body runs under the caller's restore so it stays cancellable; the release and the waiter hand-off run masked, so the tail cannot itself be interrupted.

What a slot is, who waits on it, and how a follower receives a result stay with each consumer: the cache awaits a result promise; the refresher re-decides against the freed flag. Only this claim-release discipline is shared.

Synopsis

Documentation

guardInFlight Source #

Arguments

:: (IO a -> IO a)

The enclosing mask's restore, applied to the body so it stays interruptible.

-> (SomeException -> IO ())

Run with the orphaning failure before the slot is released, to hand it to a follower waiting on the slot's result (the cache fills its result promise so the follower unblocks with the error). A consumer whose waiters instead re-decide against the freed slot passes a no-op.

-> IO ()

Free the claimed slot. Runs on every exit: a normal return, a synchronous failure, or an asynchronous exception.

-> IO a

The leader's run, executed under restore.

-> IO a 

Run a leader's body with the guarantee that its already-claimed in-flight slot is released on every exit, closing the orphan window.

Call it from inside the same mask that committed the claim, with no interruptible action between the claim and this call, passing that mask's restore. The body runs under restore so it stays cancellable; on any exit the slot is released, and on a failure the orphaning exception is first handed to any waiting follower -- both run masked, so the release cannot be orphaned in turn.