Files
Aether/crates/aether-runtime/src/concurrency.rs
T
fawney19 b5a0070023 feat: 引入 aether-runtime/cache/data/http/testkit 基础 crate,完善并发门控与审计系统
新增 crate:
- aether-runtime: 服务运行时基础设施(并发门控、分布式并发、指标、队列、优雅关闭、tracing)
- aether-cache: 通用 TTL 缓存与命名空间抽象
- aether-data: 数据访问层(PostgreSQL/Redis 后端、repository 模式)
- aether-http: HTTP 客户端封装(重试、配置)
- aether-testkit: 集成测试工具集(gateway/executor/hub/proxy fixture、等待、负载测试)

gateway 扩展:
- 引入 audit 模块(shadow 执行审计、决策链路追踪、请求审计 bundle)
- 引入 cache 模块(AuthContext 缓存、direct-plan bypass 缓存)
- 引入 data 模块(auth/candidates/config/usage/video_tasks 数据访问)
- 集成 ConcurrencyGate/DistributedConcurrencyGate 请求门控
- 新增本地 auth 拒绝、过载响应构建器
- 补充 control/auth_cache/video/concurrency 集成测试

aether-proxy 扩展:
- AppState 集成 stream_gate / distributed_stream_gate 并发门控
- 新增 ProxyAdmissionError 及准入拒绝流程
- stream_handler 补充门控饱和/不可用场景测试
- 配置与注册客户端逻辑完善

aether-hub 扩展:
- main.rs 引入运行时初始化、指标端点、健康检查
- local_relay 重构为 lib.rs 暴露公共接口
2026-03-24 15:12:56 +08:00

201 lines
6.2 KiB
Rust

use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use crate::metrics::{MetricKind, MetricLabel, MetricSample};
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum ConcurrencyError {
#[error("concurrency gate {gate} is saturated at {limit}")]
Saturated { gate: &'static str, limit: usize },
#[error("concurrency gate {gate} is closed")]
Closed { gate: &'static str },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConcurrencySnapshot {
pub limit: usize,
pub in_flight: usize,
pub available_permits: usize,
pub high_watermark: usize,
pub rejected: u64,
}
impl ConcurrencySnapshot {
pub fn to_metric_samples(&self, gate: &'static str) -> Vec<MetricSample> {
let labels = vec![MetricLabel::new("gate", gate)];
vec![
MetricSample::new(
"concurrency_in_flight",
"Current number of in-flight operations guarded by the concurrency gate.",
MetricKind::Gauge,
self.in_flight as u64,
)
.with_labels(labels.clone()),
MetricSample::new(
"concurrency_available_permits",
"Currently available permits for the concurrency gate.",
MetricKind::Gauge,
self.available_permits as u64,
)
.with_labels(labels.clone()),
MetricSample::new(
"concurrency_high_watermark",
"Highest observed in-flight count for the concurrency gate.",
MetricKind::Gauge,
self.high_watermark as u64,
)
.with_labels(labels.clone()),
MetricSample::new(
"concurrency_rejected_total",
"Number of operations rejected by the concurrency gate.",
MetricKind::Counter,
self.rejected,
)
.with_labels(labels),
]
}
}
#[derive(Debug)]
struct ConcurrencyState {
gate: &'static str,
limit: usize,
semaphore: Arc<Semaphore>,
in_flight: AtomicUsize,
high_watermark: AtomicUsize,
rejected: AtomicU64,
}
#[derive(Debug, Clone)]
pub struct ConcurrencyGate {
state: Arc<ConcurrencyState>,
}
impl ConcurrencyGate {
pub fn new(gate: &'static str, limit: usize) -> Self {
assert!(limit > 0, "concurrency gate limit must be positive");
Self {
state: Arc::new(ConcurrencyState {
gate,
limit,
semaphore: Arc::new(Semaphore::new(limit)),
in_flight: AtomicUsize::new(0),
high_watermark: AtomicUsize::new(0),
rejected: AtomicU64::new(0),
}),
}
}
pub async fn acquire(&self) -> Result<ConcurrencyPermit, ConcurrencyError> {
let permit = self
.state
.semaphore
.clone()
.acquire_owned()
.await
.map_err(|_| ConcurrencyError::Closed {
gate: self.state.gate,
})?;
Ok(ConcurrencyPermit::new(self.state.clone(), permit))
}
pub fn try_acquire(&self) -> Result<ConcurrencyPermit, ConcurrencyError> {
match self.state.semaphore.clone().try_acquire_owned() {
Ok(permit) => Ok(ConcurrencyPermit::new(self.state.clone(), permit)),
Err(tokio::sync::TryAcquireError::NoPermits) => {
self.state.rejected.fetch_add(1, Ordering::Relaxed);
Err(ConcurrencyError::Saturated {
gate: self.state.gate,
limit: self.state.limit,
})
}
Err(tokio::sync::TryAcquireError::Closed) => Err(ConcurrencyError::Closed {
gate: self.state.gate,
}),
}
}
pub fn snapshot(&self) -> ConcurrencySnapshot {
ConcurrencySnapshot {
limit: self.state.limit,
in_flight: self.state.in_flight.load(Ordering::Relaxed),
available_permits: self.state.semaphore.available_permits(),
high_watermark: self.state.high_watermark.load(Ordering::Relaxed),
rejected: self.state.rejected.load(Ordering::Relaxed),
}
}
}
#[derive(Debug)]
pub struct ConcurrencyPermit {
state: Arc<ConcurrencyState>,
_permit: OwnedSemaphorePermit,
}
impl ConcurrencyPermit {
fn new(state: Arc<ConcurrencyState>, permit: OwnedSemaphorePermit) -> Self {
let in_flight = state.in_flight.fetch_add(1, Ordering::AcqRel) + 1;
let mut observed = state.high_watermark.load(Ordering::Acquire);
while in_flight > observed {
match state.high_watermark.compare_exchange_weak(
observed,
in_flight,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(next) => observed = next,
}
}
Self {
state,
_permit: permit,
}
}
}
impl Drop for ConcurrencyPermit {
fn drop(&mut self) {
self.state.in_flight.fetch_sub(1, Ordering::AcqRel);
}
}
#[cfg(test)]
mod tests {
use super::{ConcurrencyError, ConcurrencyGate};
#[tokio::test]
async fn tracks_in_flight_and_high_watermark() {
let gate = ConcurrencyGate::new("test", 2);
let permit_a = gate.acquire().await.expect("permit a");
let permit_b = gate.acquire().await.expect("permit b");
let snapshot = gate.snapshot();
assert_eq!(snapshot.in_flight, 2);
assert_eq!(snapshot.high_watermark, 2);
assert_eq!(snapshot.available_permits, 0);
drop((permit_a, permit_b));
assert_eq!(gate.snapshot().in_flight, 0);
}
#[test]
fn rejects_when_saturated() {
let gate = ConcurrencyGate::new("test", 1);
let _permit = gate.try_acquire().expect("first permit");
let error = gate.try_acquire().expect_err("second permit should fail");
assert_eq!(
error,
ConcurrencyError::Saturated {
gate: "test",
limit: 1,
}
);
assert_eq!(gate.snapshot().rejected, 1);
}
}