2026-05-08 00:18:12 +08:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
2026-04-05 20:23:16 +08:00
|
|
|
use serde_json::json;
|
|
|
|
|
|
2026-04-07 02:50:19 +08:00
|
|
|
use aether_data_contracts::DataLayerError;
|
2026-06-30 16:54:58 +08:00
|
|
|
use aether_runtime_state::{
|
|
|
|
|
RuntimeQueueEntry, RuntimeQueueReclaimConfig, RuntimeQueueStats, RuntimeQueueStore,
|
|
|
|
|
};
|
2026-04-05 20:23:16 +08:00
|
|
|
|
|
|
|
|
use super::config::UsageRuntimeConfig;
|
|
|
|
|
use super::event::UsageEvent;
|
|
|
|
|
|
2026-05-08 00:18:12 +08:00
|
|
|
#[derive(Clone)]
|
2026-04-05 20:23:16 +08:00
|
|
|
pub struct UsageQueue {
|
2026-05-08 00:18:12 +08:00
|
|
|
runner: Arc<dyn RuntimeQueueStore>,
|
2026-04-05 20:23:16 +08:00
|
|
|
config: UsageRuntimeConfig,
|
2026-05-08 00:18:12 +08:00
|
|
|
stream: String,
|
|
|
|
|
group: String,
|
|
|
|
|
dlq_stream: String,
|
2026-04-05 20:23:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl UsageQueue {
|
|
|
|
|
pub fn new(
|
2026-05-08 00:18:12 +08:00
|
|
|
runner: Arc<dyn RuntimeQueueStore>,
|
2026-04-05 20:23:16 +08:00
|
|
|
config: UsageRuntimeConfig,
|
|
|
|
|
) -> Result<Self, DataLayerError> {
|
|
|
|
|
config.validate()?;
|
|
|
|
|
Ok(Self {
|
2026-05-08 00:18:12 +08:00
|
|
|
runner,
|
|
|
|
|
stream: config.stream_key.clone(),
|
|
|
|
|
group: config.consumer_group.clone(),
|
|
|
|
|
dlq_stream: config.dlq_stream_key.clone(),
|
2026-04-05 20:23:16 +08:00
|
|
|
config,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn ensure_consumer_group(&self) -> Result<(), DataLayerError> {
|
|
|
|
|
self.runner
|
|
|
|
|
.ensure_consumer_group(&self.stream, &self.group, "0-0")
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn enqueue(&self, event: &UsageEvent) -> Result<String, DataLayerError> {
|
|
|
|
|
let fields = event.to_stream_fields()?;
|
|
|
|
|
self.runner
|
|
|
|
|
.append_fields_with_maxlen(&self.stream, &fields, Some(self.config.stream_maxlen))
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn read_group(
|
|
|
|
|
&self,
|
2026-05-08 00:18:12 +08:00
|
|
|
consumer: &str,
|
|
|
|
|
) -> Result<Vec<RuntimeQueueEntry>, DataLayerError> {
|
2026-04-05 20:23:16 +08:00
|
|
|
self.runner
|
2026-05-08 00:18:12 +08:00
|
|
|
.read_group(
|
|
|
|
|
&self.stream,
|
|
|
|
|
&self.group,
|
|
|
|
|
consumer,
|
|
|
|
|
self.config.consumer_batch_size.max(1),
|
|
|
|
|
Some(self.config.consumer_block_ms.max(1)),
|
|
|
|
|
)
|
2026-04-05 20:23:16 +08:00
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn claim_stale(
|
|
|
|
|
&self,
|
2026-05-08 00:18:12 +08:00
|
|
|
consumer: &str,
|
2026-04-05 20:23:16 +08:00
|
|
|
start_id: &str,
|
2026-05-08 00:18:12 +08:00
|
|
|
) -> Result<Vec<RuntimeQueueEntry>, DataLayerError> {
|
|
|
|
|
self.runner
|
2026-04-05 20:23:16 +08:00
|
|
|
.claim_stale(
|
|
|
|
|
&self.stream,
|
|
|
|
|
&self.group,
|
|
|
|
|
consumer,
|
|
|
|
|
start_id,
|
2026-05-08 00:18:12 +08:00
|
|
|
RuntimeQueueReclaimConfig {
|
2026-04-05 20:23:16 +08:00
|
|
|
min_idle_ms: self.config.reclaim_idle_ms,
|
|
|
|
|
count: self.config.reclaim_count,
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-08 00:18:12 +08:00
|
|
|
.await
|
2026-04-05 20:23:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn ack_and_delete(&self, ids: &[String]) -> Result<(), DataLayerError> {
|
|
|
|
|
self.runner.ack(&self.stream, &self.group, ids).await?;
|
|
|
|
|
self.runner.delete(&self.stream, ids).await?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn push_dead_letter(
|
|
|
|
|
&self,
|
2026-05-08 00:18:12 +08:00
|
|
|
entry: &RuntimeQueueEntry,
|
2026-04-05 20:23:16 +08:00
|
|
|
error: &str,
|
|
|
|
|
) -> Result<String, DataLayerError> {
|
2026-05-08 00:18:12 +08:00
|
|
|
let fields = std::collections::BTreeMap::from([(
|
|
|
|
|
"payload".to_string(),
|
|
|
|
|
serde_json::to_string(&json!({
|
|
|
|
|
"entry_id": entry.id,
|
|
|
|
|
"fields": entry.fields,
|
|
|
|
|
"error": error,
|
|
|
|
|
}))
|
|
|
|
|
.map_err(|err| DataLayerError::UnexpectedValue(err.to_string()))?,
|
|
|
|
|
)]);
|
2026-04-05 20:23:16 +08:00
|
|
|
self.runner
|
2026-05-08 00:18:12 +08:00
|
|
|
.append_fields_with_maxlen(&self.dlq_stream, &fields, None)
|
2026-04-05 20:23:16 +08:00
|
|
|
.await
|
|
|
|
|
}
|
2026-06-30 16:54:58 +08:00
|
|
|
|
|
|
|
|
pub async fn stats(&self) -> Result<RuntimeQueueStats, DataLayerError> {
|
|
|
|
|
self.runner.stats(&self.stream, Some(&self.group)).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn dlq_stats(&self) -> Result<RuntimeQueueStats, DataLayerError> {
|
|
|
|
|
self.runner.stats(&self.dlq_stream, None).await
|
|
|
|
|
}
|
2026-04-05 20:23:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 00:18:12 +08:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
struct UsageQueueRuntimeSettings {
|
|
|
|
|
command_timeout_ms: Option<u64>,
|
|
|
|
|
read_block_ms: Option<u64>,
|
|
|
|
|
read_count: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn usage_queue_runtime_settings(config: &UsageRuntimeConfig) -> UsageQueueRuntimeSettings {
|
2026-04-05 20:23:16 +08:00
|
|
|
let read_block_ms = config.consumer_block_ms.max(1);
|
|
|
|
|
let command_timeout_ms = read_block_ms.saturating_add(2_000).max(5_000);
|
2026-05-08 00:18:12 +08:00
|
|
|
UsageQueueRuntimeSettings {
|
2026-04-05 20:23:16 +08:00
|
|
|
command_timeout_ms: Some(command_timeout_ms),
|
|
|
|
|
read_block_ms: Some(read_block_ms),
|
|
|
|
|
read_count: config.consumer_batch_size.max(1),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2026-05-08 00:18:12 +08:00
|
|
|
use super::{usage_queue_runtime_settings, UsageQueue, UsageQueueRuntimeSettings};
|
2026-04-05 20:23:16 +08:00
|
|
|
use crate::UsageRuntimeConfig;
|
2026-05-08 00:18:12 +08:00
|
|
|
use aether_runtime_state::{MemoryRuntimeStateConfig, RuntimeState};
|
|
|
|
|
use std::sync::Arc;
|
2026-04-05 20:23:16 +08:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn usage_queue_applies_runtime_block_and_batch_settings() {
|
|
|
|
|
let config = UsageRuntimeConfig {
|
|
|
|
|
enabled: true,
|
|
|
|
|
consumer_block_ms: 750,
|
|
|
|
|
consumer_batch_size: 123,
|
|
|
|
|
..UsageRuntimeConfig::default()
|
|
|
|
|
};
|
2026-05-08 00:18:12 +08:00
|
|
|
let queue = UsageQueue::new(
|
|
|
|
|
Arc::new(RuntimeState::memory(MemoryRuntimeStateConfig::default())),
|
|
|
|
|
config,
|
|
|
|
|
)
|
|
|
|
|
.expect("usage queue should build from runtime config");
|
2026-04-05 20:23:16 +08:00
|
|
|
|
|
|
|
|
assert_eq!(
|
2026-05-08 00:18:12 +08:00
|
|
|
usage_queue_runtime_settings(&queue.config),
|
|
|
|
|
UsageQueueRuntimeSettings {
|
|
|
|
|
command_timeout_ms: Some(5_000),
|
|
|
|
|
read_block_ms: Some(750),
|
|
|
|
|
read_count: 123,
|
|
|
|
|
}
|
2026-04-05 20:23:16 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|