mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-05 08:57:46 +08:00
新增 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 暴露公共接口
201 lines
6.2 KiB
Rust
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);
|
|
}
|
|
}
|