c4824ef261
- Create Helm chart structure with Chart.yaml and values.yaml - Add deployment template with container configuration and environment variables - Implement service template for gRPC port exposure - Add service account template with security configuration - Include horizontal pod autoscaler template for scaling capabilities - Add helper templates for naming and label management - Configure SMTP settings as configurable parameters in values.yaml - Set up resource limits and requests for container performance - Implement liveness and readiness probes for health checks - Add support for existing secrets and custom configurations
109 lines
2.8 KiB
Rust
109 lines
2.8 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
sync::{Arc, RwLock},
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
use tracing;
|
|
|
|
use crate::pb::email::v1::SendStatus;
|
|
|
|
const STATUS_TTL: Duration = Duration::from_secs(24 * 60 * 60);
|
|
const MAX_STATUS_ENTRIES: usize = 10_000;
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct JobStatusStore {
|
|
inner: Arc<RwLock<HashMap<u64, JobStatusEntry>>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct JobStatusEntry {
|
|
pub status: SendStatus,
|
|
pub error: Option<String>,
|
|
updated_at: Instant,
|
|
}
|
|
|
|
impl JobStatusStore {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
inner: Arc::new(RwLock::new(HashMap::new())),
|
|
}
|
|
}
|
|
|
|
pub fn set_queued(&self, id: u64) {
|
|
self.write(id, SendStatus::Queued, None);
|
|
}
|
|
|
|
pub fn set_sending(&self, id: u64) {
|
|
self.write(id, SendStatus::Sending, None);
|
|
}
|
|
|
|
pub fn set_sent(&self, id: u64) {
|
|
self.write(id, SendStatus::Sent, None);
|
|
}
|
|
|
|
pub fn set_failed(&self, id: u64, error: String) {
|
|
self.write(id, SendStatus::Failed, Some(error));
|
|
}
|
|
|
|
pub fn get(&self, id: u64) -> Option<JobStatusEntry> {
|
|
let guard = match self.inner.read() {
|
|
Ok(g) => g,
|
|
Err(poisoned) => {
|
|
tracing::error!("JobStatusStore read lock poisoned, recovering");
|
|
poisoned.into_inner()
|
|
}
|
|
};
|
|
guard.get(&id).cloned()
|
|
}
|
|
|
|
pub fn remove(&self, id: u64) {
|
|
let mut guard = match self.inner.write() {
|
|
Ok(g) => g,
|
|
Err(poisoned) => {
|
|
tracing::error!("JobStatusStore write lock poisoned, recovering");
|
|
poisoned.into_inner()
|
|
}
|
|
};
|
|
guard.remove(&id);
|
|
}
|
|
|
|
|
|
|
|
fn write(&self, id: u64, status: SendStatus, error: Option<String>) {
|
|
let mut guard = match self.inner.write() {
|
|
Ok(g) => g,
|
|
Err(poisoned) => {
|
|
tracing::error!("JobStatusStore write lock poisoned, recovering");
|
|
poisoned.into_inner()
|
|
}
|
|
};
|
|
prune_statuses(&mut guard);
|
|
guard.insert(
|
|
id,
|
|
JobStatusEntry {
|
|
status,
|
|
error,
|
|
updated_at: Instant::now(),
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
fn prune_statuses(entries: &mut HashMap<u64, JobStatusEntry>) {
|
|
let now = Instant::now();
|
|
entries.retain(|_, entry| now.duration_since(entry.updated_at) <= STATUS_TTL);
|
|
|
|
// Evict at most one entry per write to avoid O(n²) behaviour under load.
|
|
// Repeated writes will gradually shrink the map if it stays above capacity.
|
|
if entries.len() >= MAX_STATUS_ENTRIES {
|
|
if let Some(oldest_id) = entries
|
|
.iter()
|
|
.min_by_key(|(_, entry)| entry.updated_at)
|
|
.map(|(id, _)| *id)
|
|
{
|
|
entries.remove(&oldest_id);
|
|
}
|
|
}
|
|
}
|