mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-06 09:27:46 +08:00
Bound request, stream, queue, and shutdown resource lifetimes. Reduce scheduler and Redis hot-path work and isolate database maintenance. Include regression coverage, load probes, and concurrency audit results.
433 lines
17 KiB
Rust
433 lines
17 KiB
Rust
use super::*;
|
|
|
|
use super::super::{run_usage_enqueue_retry_worker, TerminalPersistenceOutcome};
|
|
|
|
const PAYLOAD_LIMIT: usize = 4 * 1024;
|
|
|
|
fn payload_config(name: &str) -> UsageRuntimeConfig {
|
|
UsageRuntimeConfig {
|
|
enabled: true,
|
|
queue_terminal_events: true,
|
|
queue_lifecycle_events: true,
|
|
stream_key: format!("usage:events:test:payload:{name}"),
|
|
consumer_group: format!("usage_consumers_payload_{name}"),
|
|
queue_payload_max_bytes: PAYLOAD_LIMIT,
|
|
consumer_block_ms: 1,
|
|
enqueue_retry_buffer_capacity: 8,
|
|
enqueue_retry_workers: 1,
|
|
enqueue_retry_initial_backoff_ms: 1,
|
|
enqueue_retry_max_backoff_ms: 2,
|
|
..UsageRuntimeConfig::default()
|
|
}
|
|
}
|
|
|
|
fn payload_event(request_id: &str, oversized: bool) -> UsageEvent {
|
|
UsageEvent {
|
|
event_type: UsageEventType::Completed,
|
|
request_id: request_id.to_string(),
|
|
timestamp_ms: 123_000,
|
|
data: UsageEventData {
|
|
user_id: Some("user-payload".to_string()),
|
|
api_key_id: Some("key-payload".to_string()),
|
|
provider_name: "openai".to_string(),
|
|
provider_id: Some("provider-payload".to_string()),
|
|
provider_api_key_id: Some("provider-key-payload".to_string()),
|
|
// Model identity is a core field, so omitting diagnostic bodies cannot make this fit.
|
|
model: if oversized {
|
|
"m".repeat(PAYLOAD_LIMIT * 2)
|
|
} else {
|
|
"gpt-5".to_string()
|
|
},
|
|
target_model: Some("gpt-5".to_string()),
|
|
api_format: Some("openai:responses".to_string()),
|
|
endpoint_api_format: Some("openai:responses".to_string()),
|
|
input_tokens: Some(100),
|
|
output_tokens: Some(25),
|
|
total_tokens: Some(125),
|
|
cache_creation_input_tokens: Some(30),
|
|
cache_creation_ephemeral_5m_input_tokens: Some(0),
|
|
cache_creation_ephemeral_1h_input_tokens: Some(30),
|
|
cache_read_input_tokens: Some(0),
|
|
total_cost_usd: Some(0.5),
|
|
actual_total_cost_usd: Some(0.25),
|
|
status_code: Some(200),
|
|
error_message: Some("preserve error presence and text".to_string()),
|
|
first_byte_time_ms: Some(12),
|
|
response_time_ms: Some(34),
|
|
request_headers: Some(json!({"x-request": "original"})),
|
|
provider_request_headers: Some(json!({"x-provider-request": "original"})),
|
|
response_headers: Some(json!({"x-provider-response": "original"})),
|
|
client_response_headers: Some(json!({"x-client-response": "original"})),
|
|
request_body: Some(json!({"reasoning": {"effort": "high"}, "input": "original"})),
|
|
request_body_state: Some(UsageBodyCaptureState::Inline),
|
|
provider_request_body: Some(json!({
|
|
"model": "gpt-5", "service_tier": "priority",
|
|
"prompt_cache_retention": "24h", "input": "original provider input"
|
|
})),
|
|
provider_request_body_state: Some(UsageBodyCaptureState::Inline),
|
|
response_body: Some(json!({"service_tier": "default", "output": "original output"})),
|
|
response_body_state: Some(UsageBodyCaptureState::Inline),
|
|
client_response_body: Some(json!({"output": "original client output"})),
|
|
client_response_body_state: Some(UsageBodyCaptureState::Inline),
|
|
request_metadata: Some(json!({
|
|
"api_key_is_standalone": true,
|
|
"plan_usage_reservation_token": "550e8400-e29b-41d4-a716-446655440000",
|
|
"plan_usage_reservation_deferred": true,
|
|
"usage_available": true,
|
|
"usage_pricing_available": true,
|
|
"provider_cache_ttl_minutes": 1440,
|
|
"provider_service_tier": "priority",
|
|
"provider_actual_service_tier": "default",
|
|
"dimensions": {
|
|
"image_count": 2, "image_size": "1024x1024", "image_quality": "high",
|
|
"image_output_format": "png", "reasoning_tokens": 0
|
|
}
|
|
})),
|
|
..UsageEventData::default()
|
|
},
|
|
}
|
|
}
|
|
|
|
async fn payload_queue(config: &UsageRuntimeConfig) -> (UsageQueue, Arc<FlakyAppendQueueStore>) {
|
|
let inner: Arc<dyn RuntimeQueueStore> =
|
|
Arc::new(RuntimeState::memory(MemoryRuntimeStateConfig::default()));
|
|
let runner = Arc::new(FlakyAppendQueueStore::new(inner, 0));
|
|
let queue = UsageQueue::new(runner.clone(), config.clone()).expect("payload queue");
|
|
queue.ensure_consumer_group().await.expect("payload group");
|
|
(queue, runner)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn terminal_oversize_uses_original_event_for_direct_fallback_without_opening_circuit() {
|
|
let config = payload_config("terminal_direct");
|
|
let (queue, runner) = payload_queue(&config).await;
|
|
let store = CloneQueueConfiguredUsageStore {
|
|
records: Arc::new(Mutex::new(Vec::new())),
|
|
queue: runner.clone(),
|
|
};
|
|
let runtime = UsageRuntime::new(config).expect("usage runtime");
|
|
let event = payload_event("payload-terminal-direct", true);
|
|
let expected = crate::build_upsert_usage_record_from_event(&event).expect("original record");
|
|
|
|
assert!(matches!(
|
|
queue.enqueue(&event).await,
|
|
Err(DataLayerError::InvalidInput(_))
|
|
));
|
|
assert_eq!(runner.append_attempts.load(Ordering::Acquire), 0);
|
|
assert_eq!(
|
|
runtime.enqueue_or_write_terminal(&store, event).await,
|
|
TerminalPersistenceOutcome::PersistedDirectly
|
|
);
|
|
{
|
|
let records = store.records.lock().expect("records lock");
|
|
assert_eq!(records.as_slice(), &[expected]);
|
|
assert_eq!(records[0].cache_read_input_tokens, Some(0));
|
|
assert_eq!(records[0].cache_creation_ephemeral_5m_input_tokens, Some(0));
|
|
assert_eq!(
|
|
records[0].request_body_state,
|
|
Some(UsageBodyCaptureState::Inline)
|
|
);
|
|
assert!(records[0].provider_request_body.is_some());
|
|
assert!(records[0].request_headers.is_some());
|
|
}
|
|
assert_eq!(
|
|
runtime
|
|
.terminal_enqueue_state
|
|
.circuit_open_until_unix_ms
|
|
.load(Ordering::Acquire),
|
|
0
|
|
);
|
|
let snapshot = runtime.metrics_snapshot();
|
|
assert_eq!(snapshot.terminal_direct_fallback_succeeded_total, 1);
|
|
assert_eq!(snapshot.enqueue_retry_scheduled_total, 0);
|
|
assert_eq!(snapshot.enqueue_retry_pending, 0);
|
|
|
|
assert_eq!(
|
|
runtime
|
|
.enqueue_or_write_terminal(&store, payload_event("payload-after-direct", false))
|
|
.await,
|
|
TerminalPersistenceOutcome::Queued
|
|
);
|
|
assert_eq!(runner.successful_appends.load(Ordering::Acquire), 1);
|
|
assert_eq!(store.records.lock().expect("records lock").len(), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn terminal_oversize_direct_failure_preserves_first_byte_and_does_not_retry_or_open_circuit()
|
|
{
|
|
let config = payload_config("terminal_failed");
|
|
let (_, runner) = payload_queue(&config).await;
|
|
let store = FailingWriteQueueConfiguredUsageStore {
|
|
queue: runner.clone(),
|
|
upsert_attempts: Arc::new(AtomicUsize::new(0)),
|
|
};
|
|
let runtime = UsageRuntime::new(config).expect("usage runtime");
|
|
let request_id = "payload-terminal-failed";
|
|
let generation = runtime
|
|
.lifecycle_coalescer
|
|
.mark_first_byte(request_id)
|
|
.await
|
|
.expect("first byte");
|
|
|
|
assert_eq!(
|
|
runtime
|
|
.enqueue_or_write_terminal(&store, payload_event(request_id, true))
|
|
.await,
|
|
TerminalPersistenceOutcome::Failed
|
|
);
|
|
assert!(
|
|
runtime
|
|
.lifecycle_coalescer
|
|
.first_byte_is_current(request_id, generation)
|
|
.await
|
|
);
|
|
assert_eq!(runner.append_attempts.load(Ordering::Acquire), 0);
|
|
assert_eq!(store.upsert_attempts.load(Ordering::Acquire), 1);
|
|
assert_eq!(
|
|
runtime
|
|
.terminal_enqueue_state
|
|
.circuit_open_until_unix_ms
|
|
.load(Ordering::Acquire),
|
|
0
|
|
);
|
|
let snapshot = runtime.metrics_snapshot();
|
|
assert_eq!(snapshot.terminal_direct_fallback_failed_total, 1);
|
|
assert_eq!(snapshot.terminal_enqueue_deferred_dropped_total, 1);
|
|
assert_eq!(snapshot.enqueue_retry_scheduled_total, 0);
|
|
assert_eq!(snapshot.enqueue_retry_pending, 0);
|
|
|
|
assert_eq!(
|
|
runtime
|
|
.enqueue_or_write_terminal(&store, payload_event("payload-after-failed", false))
|
|
.await,
|
|
TerminalPersistenceOutcome::Queued
|
|
);
|
|
assert_eq!(runner.successful_appends.load(Ordering::Acquire), 1);
|
|
assert_eq!(store.upsert_attempts.load(Ordering::Acquire), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn terminal_oversize_direct_failure_stays_failed_when_primary_enqueue_is_deferred() {
|
|
for (name, circuit_open) in [("circuit_open", true), ("in_flight_limit", false)] {
|
|
let mut config = payload_config(name);
|
|
config.terminal_enqueue_max_in_flight = 1;
|
|
let (_, runner) = payload_queue(&config).await;
|
|
let store = FailingWriteQueueConfiguredUsageStore {
|
|
queue: runner.clone(),
|
|
upsert_attempts: Arc::new(AtomicUsize::new(0)),
|
|
};
|
|
let runtime = UsageRuntime::new(config).expect("usage runtime");
|
|
let request_id = format!("payload-terminal-{name}");
|
|
let generation = runtime
|
|
.lifecycle_coalescer
|
|
.mark_first_byte(&request_id)
|
|
.await
|
|
.expect("first byte");
|
|
let original_deadline = if circuit_open {
|
|
let deadline = super::super::now_unix_ms().saturating_add(60_000);
|
|
runtime.terminal_enqueue_state.open_circuit(deadline);
|
|
deadline
|
|
} else {
|
|
0
|
|
};
|
|
let held_guard = if circuit_open {
|
|
None
|
|
} else {
|
|
Some(
|
|
runtime
|
|
.terminal_enqueue_state
|
|
.try_acquire_in_flight(1)
|
|
.expect("hold the only enqueue slot"),
|
|
)
|
|
};
|
|
|
|
assert_eq!(
|
|
timeout(
|
|
Duration::from_secs(2),
|
|
runtime.enqueue_or_write_terminal(&store, payload_event(&request_id, true)),
|
|
)
|
|
.await
|
|
.expect("bounded terminal fallback"),
|
|
TerminalPersistenceOutcome::Failed,
|
|
"{name} must not report an oversized event as buffered"
|
|
);
|
|
assert!(
|
|
runtime
|
|
.lifecycle_coalescer
|
|
.first_byte_is_current(&request_id, generation)
|
|
.await
|
|
);
|
|
assert_eq!(runner.append_attempts.load(Ordering::Acquire), 0);
|
|
assert_eq!(store.upsert_attempts.load(Ordering::Acquire), 1);
|
|
assert_eq!(
|
|
runtime
|
|
.terminal_enqueue_state
|
|
.circuit_open_until_unix_ms
|
|
.load(Ordering::Acquire),
|
|
original_deadline
|
|
);
|
|
let snapshot = runtime.metrics_snapshot();
|
|
assert_eq!(snapshot.terminal_direct_fallback_failed_total, 1);
|
|
assert_eq!(snapshot.terminal_enqueue_deferred_dropped_total, 1);
|
|
assert_eq!(snapshot.terminal_enqueue_deferred_retry_total, 0);
|
|
assert_eq!(snapshot.enqueue_retry_permanent_failure_total, 1);
|
|
assert_eq!(snapshot.enqueue_retry_scheduled_total, 0);
|
|
assert_eq!(snapshot.enqueue_retry_pending, 0);
|
|
assert_eq!(
|
|
runtime.terminal_enqueue_state.in_flight(),
|
|
u64::from(!circuit_open)
|
|
);
|
|
drop(held_guard);
|
|
assert_eq!(runtime.terminal_enqueue_state.in_flight(), 0);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn retry_worker_discards_oversize_and_drains_next_event_on_the_same_shard() {
|
|
let config = payload_config("retry_drain");
|
|
let (queue, runner) = payload_queue(&config).await;
|
|
let (sender, receiver) = mpsc::channel(2);
|
|
let dispatcher = UsageEnqueueRetryDispatcher {
|
|
senders: vec![sender],
|
|
metrics: Arc::new(Default::default()),
|
|
};
|
|
let metrics_view = UsageEnqueueRetryDispatcher {
|
|
senders: Vec::new(),
|
|
metrics: Arc::clone(&dispatcher.metrics),
|
|
};
|
|
for (request_id, oversized) in [
|
|
("payload-retry-oversize", true),
|
|
("payload-retry-small", false),
|
|
] {
|
|
// Bypass admission to exercise the worker's defense for an already buffered event.
|
|
assert!(dispatcher
|
|
.schedule_item(
|
|
queue.clone(),
|
|
payload_event(request_id, oversized),
|
|
"terminal",
|
|
Some("prior transient failure"),
|
|
)
|
|
.is_some());
|
|
}
|
|
assert_eq!(dispatcher.pending(), 2);
|
|
drop(dispatcher);
|
|
|
|
// Run the real worker as a cancellable future, so a regression cannot leave a detached retry.
|
|
timeout(
|
|
Duration::from_secs(2),
|
|
run_usage_enqueue_retry_worker(0, config, receiver, Arc::clone(&metrics_view.metrics)),
|
|
)
|
|
.await
|
|
.expect("the permanent failure must not block the shard");
|
|
|
|
assert_eq!(metrics_view.permanent_failure_total(), 1);
|
|
assert_eq!(metrics_view.recovered_total(), 1);
|
|
assert_eq!(metrics_view.pending(), 0);
|
|
assert_eq!(runner.append_attempts.load(Ordering::Acquire), 1);
|
|
let entries = queue
|
|
.read_group("payload-retry-reader")
|
|
.await
|
|
.expect("queue read");
|
|
assert_eq!(entries.len(), 1);
|
|
assert_eq!(
|
|
UsageEvent::from_stream_fields(&entries[0].fields)
|
|
.expect("queued event")
|
|
.request_id,
|
|
"payload-retry-small"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn retry_dispatcher_rejects_oversize_for_permanent_and_transient_causes_without_consuming_slots(
|
|
) {
|
|
let config = payload_config("retry_reject");
|
|
let (queue, runner) = payload_queue(&config).await;
|
|
let (sender, receiver) = mpsc::channel(1);
|
|
let dispatcher = UsageEnqueueRetryDispatcher {
|
|
senders: vec![sender],
|
|
metrics: Arc::new(Default::default()),
|
|
};
|
|
let oversized = payload_event("payload-rejected", true);
|
|
let error = queue.enqueue(&oversized).await.expect_err("oversize input");
|
|
assert!(matches!(error, DataLayerError::InvalidInput(_)));
|
|
assert!(!dispatcher.schedule(queue.clone(), oversized, "terminal", error));
|
|
for cause in [
|
|
DataLayerError::TimedOut("primary enqueue was deferred".to_string()),
|
|
DataLayerError::Redis("prior transient failure".to_string()),
|
|
] {
|
|
assert!(!dispatcher.schedule(
|
|
queue.clone(),
|
|
payload_event("payload-rejected-transient-cause", true),
|
|
"terminal",
|
|
cause,
|
|
));
|
|
}
|
|
assert_eq!(dispatcher.permanent_failure_total(), 3);
|
|
assert_eq!(dispatcher.pending(), 0);
|
|
assert_eq!(dispatcher.scheduled_total(), 0);
|
|
assert!(dispatcher.schedule(
|
|
queue,
|
|
payload_event("payload-after-reject", false),
|
|
"terminal",
|
|
DataLayerError::Redis("retryable failure".to_string()),
|
|
));
|
|
let metrics_view = UsageEnqueueRetryDispatcher {
|
|
senders: Vec::new(),
|
|
metrics: Arc::clone(&dispatcher.metrics),
|
|
};
|
|
drop(dispatcher);
|
|
|
|
timeout(
|
|
Duration::from_secs(2),
|
|
run_usage_enqueue_retry_worker(0, config, receiver, Arc::clone(&metrics_view.metrics)),
|
|
)
|
|
.await
|
|
.expect("retry worker drain");
|
|
|
|
assert_eq!(metrics_view.permanent_failure_total(), 3);
|
|
assert_eq!(metrics_view.recovered_total(), 1);
|
|
assert_eq!(metrics_view.pending(), 0);
|
|
assert_eq!(runner.append_attempts.load(Ordering::Acquire), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn lifecycle_oversize_does_not_open_circuit_or_block_the_next_lifecycle_event() {
|
|
let config = payload_config("lifecycle");
|
|
let (queue, runner) = payload_queue(&config).await;
|
|
let store = CloneQueueConfiguredUsageStore {
|
|
records: Arc::new(Mutex::new(Vec::new())),
|
|
queue: runner.clone(),
|
|
};
|
|
let runtime = UsageRuntime::new(config).expect("usage runtime");
|
|
let mut oversized = payload_event("payload-lifecycle-oversize", true);
|
|
oversized.event_type = UsageEventType::Streaming;
|
|
|
|
assert!(!runtime.enqueue_lifecycle_event(&store, oversized).await);
|
|
assert_eq!(
|
|
runtime
|
|
.lifecycle_enqueue_state
|
|
.circuit_open_until_unix_ms
|
|
.load(Ordering::Acquire),
|
|
0
|
|
);
|
|
assert_eq!(runner.append_attempts.load(Ordering::Acquire), 0);
|
|
let snapshot = runtime.metrics_snapshot();
|
|
assert_eq!(snapshot.enqueue_retry_scheduled_total, 0);
|
|
assert_eq!(snapshot.enqueue_retry_pending, 0);
|
|
assert!(store.records.lock().expect("records lock").is_empty());
|
|
|
|
let mut small = payload_event("payload-lifecycle-small", false);
|
|
small.event_type = UsageEventType::Streaming;
|
|
assert!(runtime.enqueue_lifecycle_event(&store, small).await);
|
|
assert_eq!(runner.successful_appends.load(Ordering::Acquire), 1);
|
|
let entries = queue
|
|
.read_group("payload-lifecycle-reader")
|
|
.await
|
|
.expect("queue read");
|
|
assert_eq!(entries.len(), 1);
|
|
let queued = UsageEvent::from_stream_fields(&entries[0].fields).expect("queued lifecycle");
|
|
assert_eq!(queued.request_id, "payload-lifecycle-small");
|
|
assert_eq!(queued.event_type, UsageEventType::Streaming);
|
|
assert_eq!(queued.data.first_byte_time_ms, Some(12));
|
|
}
|