ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Server.Admission

Description

Brief-wait admission control for metadata-bearing serve work.

The handle caps concurrent operations and retains a bounded room of waiters: an operation acquires a slot immediately, waits briefly for one, or is refused. This bounds aggregate metadata residency by construction while absorbing a burst that merely brushes the cap, so near-capacity load degrades into short queueing delay rather than a refusal the client immediately retries. Refusal is reserved for genuine overload: a waiting room already at its bound (the deep-overflow band, refused instantly and cheaply) or a wait that outlives its budget.

Instant shedding is self-amplifying under a hammering client: each refusal is answered in microseconds, so the client comes straight back, and the refusal work itself competes for the cores the admitted work needs. Waiting in-process is a blocked green thread -- nearly free -- and every slot release goes to work that has already arrived. The wait budget equals the shed path's Retry-After: 1 hint, so a request is never refused faster than the client would have been told to come back.

Two fairness properties, one deliberate limit:

  • A newcomer never jumps a non-empty waiting room: a freed slot is only taken directly when no one is waiting, so arrival order is respected between the room and the door.
  • Within the room, wake-up order is not FIFO (STM retry semantics: all waiters race, first commit wins). With the room bounded at the capacity and slot turnover far faster than the budget, starvation is not a practical concern, and strict ticketing is complexity this surface has not earned.

Acquired slots are released across normal completion, failure, and asynchronous cancellation. The waits run masked: a blocked STM retry remains interruptible (a cancellation lands and aborts the transaction, taking nothing), while a committed acquire returns with exceptions still masked, so a slot can never be lost between acquisition and the protected run.

Synopsis

Documentation

data ServeAdmission Source #

A process-wide serve admission handle. The constructor is hidden so only the checked acquire/wait/release operations can mutate its capacity and waiting room.

newServeAdmission :: Int -> IO ServeAdmission Source #

Allocate a bounded handle with the given positive capacity, a waiting room of the same size, and the serveAdmissionWaitMicros budget.

The room equals the capacity so a burst of twice the cap is absorbed as brief queueing while anything deeper still gets the instant, cheap refusal -- bounding both waiting memory and worst-case latency. Configuration parsing enforces the positive-capacity precondition; the unchecked integer stays at this internal composition boundary so every request pays only an STM transaction, not another validation step.

unlimitedServeAdmission :: ServeAdmission Source #

An admission handle that never refuses, for embedded applications and tests whose subject is unrelated to overload.

withServeAdmission :: MonadUnliftIO m => MetricsPort -> ServeAdmission -> m a -> m (Maybe a) Source #

Run an action within the admission bound. Nothing means the request was refused -- the waiting room was full, or no slot freed within the wait budget -- and the caller should shed it.

A request that had to wait records ecluse.serve.admission.queued on admission, so the queue's work is visible beside the in-flight gauge and the shed decisions.

serveAdmissionWaitMicros :: Int Source #

How long an operation finding the cap busy waits for a slot before it is refused: deliberately equal to the shed path's Retry-After: 1 hint, so a refusal only ever reaches a client that has already waited one full retry interval in-process, where the wait is a blocked green thread instead of a wire round trip.

Internals exported for testing

newServeAdmissionTuned :: Int -> Int -> Int -> IO ServeAdmission Source #

Allocate a bounded handle with an explicit waiting-room bound and wait budget (microseconds), so a test can exercise the queueing behaviour without real-second sleeps. Production goes through newServeAdmission, which fixes both from the capacity; a room of zero reproduces pure acquire-or-refuse admission.