use std::{
collections::{HashMap, HashSet, VecDeque},
sync::{Arc, Mutex},
};
use scv_core::ToolError;
use tokio::sync::Notify;
use tokio_util::sync::CancellationToken;
use crate::sync::lock;
#[derive(Debug, Default)]
pub(in crate::delegate) struct Lanes {
state: Mutex<State>,
changed: Notify,
}
#[derive(Debug, Default)]
struct State {
next: u64,
lanes: HashMap<String, VecDeque<u64>>,
takeovers: HashSet<u64>,
jobs: HashMap<u64, String>,
}
#[derive(Debug)]
pub(in crate::delegate) struct Place {
lanes: Arc<Lanes>,
handle: String,
id: u64,
pub(in crate::delegate) ahead: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(in crate::delegate) enum Front {
Takeover,
Job(String),
Call,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::delegate) enum Unreserved {
TakenOver,
NotAJob,
}
impl Lanes {
pub(in crate::delegate) fn join(self: &Arc<Self>, handle: &str) -> Place {
self.join_as(handle, false)
}
pub(in crate::delegate) fn join_takeover(self: &Arc<Self>, handle: &str) -> Place {
self.join_as(handle, true)
}
pub(in crate::delegate) fn held_by_takeover(&self, handle: &str) -> bool {
self.front(handle) == Some(Front::Takeover)
}
pub(in crate::delegate) fn has_takeover(&self, handle: &str) -> bool {
let state = lock(&self.state);
state
.lanes
.get(handle)
.is_some_and(|lane| lane.iter().any(|id| state.takeovers.contains(id)))
}
pub(in crate::delegate) fn front(&self, handle: &str) -> Option<Front> {
let state = lock(&self.state);
let id = state.lanes.get(handle).and_then(VecDeque::front)?;
Some(state.front(*id))
}
pub(in crate::delegate) fn reserve_takeover(
self: &Arc<Self>,
handle: &str,
) -> Result<(Place, Option<String>), Unreserved> {
let mut state = lock(&self.state);
let lane = state.lanes.get(handle);
if lane.is_some_and(|lane| lane.iter().any(|id| state.takeovers.contains(id))) {
return Err(Unreserved::TakenOver);
}
let ahead = match lane.and_then(VecDeque::front) {
None => None,
Some(id) => match state.front(*id) {
Front::Job(job) => Some(job),
Front::Takeover => return Err(Unreserved::TakenOver),
Front::Call => return Err(Unreserved::NotAJob),
},
};
state.next += 1;
let id = state.next;
let lane = state.lanes.entry(handle.to_owned()).or_default();
let at = lane.len().min(1);
lane.insert(at, id);
state.takeovers.insert(id);
Ok((
Place {
lanes: Arc::clone(self),
handle: handle.to_owned(),
id,
ahead: at,
},
ahead,
))
}
fn join_as(self: &Arc<Self>, handle: &str, takeover: bool) -> Place {
let mut state = lock(&self.state);
state.next += 1;
let id = state.next;
let lane = state.lanes.entry(handle.to_owned()).or_default();
let ahead = lane.len();
lane.push_back(id);
if takeover {
state.takeovers.insert(id);
}
Place {
lanes: Arc::clone(self),
handle: handle.to_owned(),
id,
ahead,
}
}
}
impl State {
fn front(&self, id: u64) -> Front {
if self.takeovers.contains(&id) {
Front::Takeover
} else if let Some(job) = self.jobs.get(&id) {
Front::Job(job.clone())
} else {
Front::Call
}
}
}
impl Place {
pub(in crate::delegate) fn handle(&self) -> &str {
&self.handle
}
pub(in crate::delegate) fn run_as(&self, job: &str) {
lock(&self.lanes.state).jobs.insert(self.id, job.to_owned());
}
fn is_first(&self) -> bool {
lock(&self.lanes.state)
.lanes
.get(&self.handle)
.and_then(VecDeque::front)
== Some(&self.id)
}
pub(in crate::delegate) async fn wait_first(
&self,
cancellation: &CancellationToken,
) -> Result<(), ToolError> {
loop {
let changed = self.lanes.changed.notified();
if self.is_first() {
return Ok(());
}
tokio::select! {
() = changed => {}
() = cancellation.cancelled() => {
return Err(ToolError::cancelled(format!(
"cancelled while waiting for conversation {}",
self.handle
)));
}
}
}
}
}
impl Drop for Place {
fn drop(&mut self) {
let mut state = lock(&self.lanes.state);
if let Some(lane) = state.lanes.get_mut(&self.handle) {
lane.retain(|id| *id != self.id);
if lane.is_empty() {
state.lanes.remove(&self.handle);
}
}
state.takeovers.remove(&self.id);
state.jobs.remove(&self.id);
drop(state);
self.lanes.changed.notify_waiters();
}
}
#[cfg(test)]
mod tests;