use std::collections::BTreeMap;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::time::Duration;
use super::{Call, Capture, Invocation, unknown};
const HOLD_WAITS: u32 = 240;
const HOLD_TICK: Duration = Duration::from_millis(125);
const TTL_SECONDS: i64 = 3600;
#[derive(Debug, Clone, PartialEq)]
struct Slot {
client: String,
by: String,
invocation: Invocation,
taken: bool,
capture: Option<Capture>,
at: i64,
}
#[derive(Default)]
struct Slots {
live: BTreeMap<String, Slot>,
seq: u64,
}
type MailCell = Arc<Mutex<Slots>>;
fn lock_mail(cell: &MailCell) -> MutexGuard<'_, Slots> {
cell.lock().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Clone)]
pub struct Mailbox {
cell: MailCell,
waits: u32,
tick: Duration,
}
impl Default for Mailbox {
fn default() -> Self {
Self {
cell: MailCell::default(),
waits: HOLD_WAITS,
tick: HOLD_TICK,
}
}
}
impl Mailbox {
pub fn holding(waits: u32, tick: Duration) -> Self {
Self {
cell: MailCell::default(),
waits,
tick,
}
}
pub fn post(&self, now: i64, by: &str, call: &Call) -> String {
let mut slots = lock_mail(&self.cell);
slots.live.retain(|_, slot| now - slot.at <= TTL_SECONDS);
slots.seq += 1;
let id = format!("inv-{}", slots.seq);
slots.live.insert(
id.clone(),
Slot {
client: call.client.clone(),
by: by.to_owned(),
invocation: Invocation {
id: id.clone(),
tool: call.tool.clone(),
input: call.input.clone(),
},
taken: false,
capture: None,
at: now,
},
);
id
}
pub fn take(&self, client: &str) -> Vec<Invocation> {
for _ in 0..self.waits {
let taken = self.drain(client);
if !taken.is_empty() {
return taken;
}
std::thread::sleep(self.tick);
}
self.drain(client)
}
fn drain(&self, client: &str) -> Vec<Invocation> {
let mut slots = lock_mail(&self.cell);
let mut out = Vec::new();
for slot in slots.live.values_mut() {
if slot.client == client && !slot.taken {
slot.taken = true;
out.push(slot.invocation.clone());
}
}
out
}
pub fn complete(
&self,
client: &str,
invocation: &str,
capture: &Capture,
) -> Result<Capture, String> {
let mut slots = lock_mail(&self.cell);
let slot = slots
.live
.get_mut(invocation)
.filter(|slot| slot.client == client)
.ok_or_else(|| unknown(invocation))?;
slot.capture = Some(capture.clone());
Ok(capture.clone())
}
pub fn collect(&self, by: &str, invocation: &str) -> Result<Option<Capture>, String> {
let mut slots = lock_mail(&self.cell);
let slot = slots
.live
.get(invocation)
.filter(|slot| slot.by == by)
.ok_or_else(|| unknown(invocation))?;
let Some(capture) = slot.capture.clone() else {
return Ok(None);
};
slots.live.remove(invocation);
Ok(Some(capture))
}
}
#[cfg(test)]
mod tests;