arcly-http 0.1.1

Enterprise-grade NestJS-inspired web framework on axum: zero-lock DI, declarative controllers, multi-tenant data routing, transactional outbox, ABAC, and a self-documenting OpenAPI surface
Documentation
//! Bulkhead — bounded concurrency per endpoint group.
//!
//! The circuit breaker protects against a dependency that *fails*; the
//! bulkhead protects against one that is merely *slow*. Without it, a single
//! laggy downstream lets in-flight requests pile up until every worker on the
//! pod is parked on that dependency — the classic slow-loris cascade that a
//! breaker never trips on (nothing is erroring, everything is just waiting).
//!
//! `tokio::sync::Semaphore` is a lock-free waiter queue, and `try_acquire`
//! never waits at all — saturation answers `503` immediately so the caller
//! retries on a healthier replica, instead of queueing latency on this one.
//!
//! ```ignore
//! static PAYMENT: Bulkhead = Bulkhead::new("payment", 32);
//!
//! async fn create_order(ctx: RequestContext /* ... */) -> Result<Json<Value>, HttpException> {
//!     let _permit = PAYMENT.try_enter()?;   // 503 when saturated
//!     payment.charge(/* ... */).await               // permit released on drop
//! }
//! ```

use tokio::sync::{Semaphore, SemaphorePermit};

use crate::web::Error;

pub struct Bulkhead {
    sem: Semaphore,
    name: &'static str,
}

impl Bulkhead {
    /// `const`-constructible so it can live in a `static` next to the handler.
    pub const fn new(name: &'static str, max_concurrent: usize) -> Self {
        Self {
            sem: Semaphore::const_new(max_concurrent),
            name,
        }
    }

    /// Non-blocking admission: shed (`503`) instead of queueing when full.
    /// Hold the returned permit for the protected section; drop releases it.
    pub fn try_enter(&self) -> Result<SemaphorePermit<'_>, Error> {
        self.sem.try_acquire().map_err(|_| {
            metrics::counter!("bulkhead_rejected_total", "name" => self.name).increment(1);
            Error::ServiceUnavailable("bulkhead saturated")
        })
    }

    /// Currently available slots (for dashboards/tests).
    pub fn available(&self) -> usize {
        self.sem.available_permits()
    }
}