use std::collections::BTreeMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Weak};
use std::time::Instant;
use serde::{Deserialize, Serialize};
use super::{Fabric, Inner, Outcome};
use crate::nerve::motor::MotionError;
use crate::nerve::neuron::NeuronKind;
use crate::nerve::signal::{NeuronId, Signal};
use crate::nerve::sync::{Mutex, MutexGuard};
const WAITING: usize = 1024;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Nerve {
pub(super) name: String,
pub(super) in_flight: usize,
}
impl Nerve {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
in_flight: 16,
}
}
pub fn in_flight(mut self, n: usize) -> Self {
self.in_flight = n.max(1);
self
}
}
impl From<&str> for Nerve {
fn from(name: &str) -> Self {
Self::new(name)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Key {
whenever: bool,
deadline: Option<Instant>,
sequence: u64,
}
struct LaneState<T> {
running: usize,
waiting: BTreeMap<Key, T>,
sequence: u64,
}
pub(crate) struct Lane<T = (NeuronId, Signal)> {
pub(crate) name: String,
limit: usize,
room: usize,
state: Mutex<LaneState<T>>,
waited: AtomicU64,
dropped: AtomicU64,
}
pub(crate) enum Entered<T> {
Go(T),
Waits { dropped: Option<T> },
Full(T),
}
pub(crate) struct Released<T> {
pub(crate) next: Option<T>,
pub(crate) expired: Vec<T>,
pub(crate) unwanted: Vec<T>,
}
impl<T> Lane<T> {
pub(super) fn new(nerve: Nerve) -> Self {
Self::with_room(nerve, WAITING)
}
fn with_room(nerve: Nerve, room: usize) -> Self {
Self {
name: nerve.name,
limit: nerve.in_flight,
room,
state: Mutex::new(LaneState {
running: 0,
waiting: BTreeMap::new(),
sequence: 0,
}),
waited: AtomicU64::new(0),
dropped: AtomicU64::new(0),
}
}
fn state(&self) -> MutexGuard<'_, LaneState<T>> {
self.state.lock().unwrap_or_else(|e| e.into_inner())
}
pub(crate) fn metrics(&self) -> NerveLaneMetrics {
let state = self.state();
NerveLaneMetrics {
name: self.name.clone(),
in_flight: state.running as u64,
limit: self.limit as u64,
waiting: state.waiting.len() as u64,
waited: self.waited.load(Ordering::Relaxed),
dropped: self.dropped.load(Ordering::Relaxed),
}
}
fn enter(&self, deadline: Option<Instant>, item: T) -> Entered<T> {
let mut state = self.state();
if state.running < self.limit && state.waiting.is_empty() {
state.running += 1;
return Entered::Go(item);
}
state.sequence += 1;
let key = Key {
whenever: deadline.is_none(),
deadline,
sequence: state.sequence,
};
let mut dropped = None;
if state.waiting.len() >= self.room {
match state.waiting.last_key_value().map(|(k, _)| *k) {
Some(least) if key < least => {
dropped = state.waiting.pop_last().map(|(_, item)| item);
}
_ => {
drop(state);
self.dropped.fetch_add(1, Ordering::Relaxed);
return Entered::Full(item);
}
}
}
state.waiting.insert(key, item);
drop(state);
if dropped.is_some() {
self.dropped.fetch_add(1, Ordering::Relaxed);
}
self.waited.fetch_add(1, Ordering::Relaxed);
Entered::Waits { dropped }
}
fn release(&self, now: Instant, wanted: impl Fn(&T) -> bool) -> Released<T> {
let mut released = Released {
next: None,
expired: Vec::new(),
unwanted: Vec::new(),
};
let mut state = self.state();
state.running = state.running.saturating_sub(1);
while state.running < self.limit {
let Some((key, item)) = state.waiting.pop_first() else {
break;
};
if key.deadline.is_some_and(|d| d <= now) {
released.expired.push(item);
} else if !wanted(&item) {
released.unwanted.push(item);
} else {
state.running += 1;
released.next = Some(item);
break;
}
}
drop(state);
self.dropped
.fetch_add(released.expired.len() as u64, Ordering::Relaxed);
released
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct NerveLaneMetrics {
pub name: String,
pub in_flight: u64,
pub limit: u64,
pub waiting: u64,
pub waited: u64,
pub dropped: u64,
}
#[derive(Clone)]
pub(crate) struct Ticket(Arc<Held>);
struct Held {
fabric: Weak<Inner>,
nerve: usize,
}
impl Drop for Held {
fn drop(&mut self) {
if let Some(inner) = self.fabric.upgrade() {
Fabric(inner).release(self.nerve);
}
}
}
impl std::fmt::Debug for Ticket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Ticket({})", self.0.nerve)
}
}
impl Fabric {
pub(super) fn enter(&self, to: NeuronId, mut signal: Signal) -> Result<Signal, Outcome> {
let cell = &self.0.cells[to.index()];
let Some(nerve) = cell.nerve.filter(|_| cell.kind == NeuronKind::Effector) else {
return Ok(signal);
};
signal.ticket = None;
let lane = &self.0.nerves[nerve];
match lane.enter(signal.urgency.deadline, (to, signal)) {
Entered::Go((_, mut signal)) => {
signal.ticket = Some(self.ticket(nerve));
Ok(signal)
}
Entered::Waits { dropped } => {
if let Some((_, dropped)) = dropped {
dropped.report(Err(full(&lane.name)));
}
Err(Outcome {
queued: 1,
..Outcome::default()
})
}
Entered::Full((_, signal)) => {
signal.report(Err(full(&lane.name)));
Err(Outcome {
dropped: 1,
..Outcome::default()
})
}
}
}
fn ticket(&self, nerve: usize) -> Ticket {
Ticket(Arc::new(Held {
fabric: Arc::downgrade(&self.0),
nerve,
}))
}
fn release(&self, nerve: usize) {
let lane = &self.0.nerves[nerve];
let released = lane.release(Instant::now(), |(_, signal)| !signal.abandoned());
for (_, signal) in released.expired {
signal.report(Err(MotionError::TimedOut));
}
if let Some((to, mut signal)) = released.next {
signal.ticket = Some(self.ticket(nerve));
let fabric = self.clone();
self.efferent().spawn(async move {
fabric.deliver(to, signal);
});
}
}
}
fn full(nerve: &str) -> MotionError {
MotionError::failed(format!("the nerve '{nerve}' is full"))
}
#[cfg(all(test, furnace_loom))]
mod model_check;
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::time::Duration;
use super::*;
struct Probe(Arc<Lane<Probe>>);
impl Drop for Probe {
fn drop(&mut self) {
self.0.metrics();
}
}
#[test]
fn what_leaves_a_lane_is_dropped_out_of_its_lock() {
let lane = Arc::new(Lane::with_room(Nerve::new("arm").in_flight(1), 1));
let probe = || Probe(lane.clone());
let soon = Some(Instant::now() + Duration::from_secs(3600));
let later = Some(Instant::now() + Duration::from_secs(7200));
let Entered::Go(running) = lane.enter(None, probe()) else {
panic!("an idle lane lets it on");
};
assert!(matches!(
lane.enter(later, probe()),
Entered::Waits { dropped: None }
));
let Entered::Waits { dropped } = lane.enter(soon, probe()) else {
panic!("the more urgent one waits");
};
drop(dropped.expect("the least urgent is dropped"));
let Entered::Full(full) = lane.enter(later, probe()) else {
panic!("the lane is full");
};
drop(full);
drop(running);
let released = lane.release(Instant::now() + Duration::from_secs(4000), |_| true);
assert!(released.next.is_none());
assert_eq!(released.expired.len(), 1);
drop(released);
let metrics = lane.metrics();
assert_eq!(
(metrics.in_flight, metrics.waiting, metrics.dropped),
(0, 0, 3)
);
}
}