use std::collections::BTreeMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::time::Instant;
use serde::{Deserialize, Serialize};
use super::{Fabric, Inner, Outcome};
use crate::nerve::motor::MotionError;
use crate::nerve::signal::{NeuronId, Signal};
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 {
running: usize,
waiting: BTreeMap<Key, (NeuronId, Signal)>,
sequence: u64,
}
pub(crate) struct Lane {
pub(crate) name: String,
limit: usize,
state: Mutex<LaneState>,
waited: AtomicU64,
dropped: AtomicU64,
}
impl Lane {
pub(super) fn new(nerve: Nerve) -> Self {
Self {
name: nerve.name,
limit: nerve.in_flight,
state: Mutex::new(LaneState {
running: 0,
waiting: BTreeMap::new(),
sequence: 0,
}),
waited: AtomicU64::new(0),
dropped: AtomicU64::new(0),
}
}
fn state(&self) -> std::sync::MutexGuard<'_, LaneState> {
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),
}
}
}
#[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,
from: NeuronId,
to: NeuronId,
mut signal: Signal,
) -> Result<Signal, Outcome> {
let Some(nerve) = self.0.cells[to.index()].nerve else {
return Ok(signal);
};
if self.0.cells[from.index()].nerve == Some(nerve) {
return Ok(signal);
}
let lane = &self.0.nerves[nerve];
let mut state = lane.state();
if state.running < lane.limit && state.waiting.is_empty() {
state.running += 1;
drop(state);
signal.ticket = Some(self.ticket(nerve));
return Ok(signal);
}
state.sequence += 1;
let key = Key {
whenever: signal.urgency.deadline.is_none(),
deadline: signal.urgency.deadline,
sequence: state.sequence,
};
if state.waiting.len() >= WAITING {
let least = state.waiting.last_key_value().map(|(k, _)| *k);
match least {
Some(least) if key < least => {
if let Some((_, (_, dropped))) = state.waiting.pop_last() {
dropped.report(Err(full(&lane.name)));
}
}
_ => {
drop(state);
lane.dropped.fetch_add(1, Ordering::Relaxed);
signal.report(Err(full(&lane.name)));
return Err(Outcome {
dropped: 1,
..Outcome::default()
});
}
}
lane.dropped.fetch_add(1, Ordering::Relaxed);
}
state.waiting.insert(key, (to, signal));
lane.waited.fetch_add(1, Ordering::Relaxed);
Err(Outcome {
queued: 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 mut state = lane.state();
state.running = state.running.saturating_sub(1);
let now = Instant::now();
while state.running < lane.limit {
let Some((_, (to, mut signal))) = state.waiting.pop_first() else {
return;
};
if signal.urgency.deadline.is_some_and(|d| d <= now) {
lane.dropped.fetch_add(1, Ordering::Relaxed);
signal.report(Err(MotionError::TimedOut));
continue;
}
if signal.abandoned() {
continue;
}
state.running += 1;
drop(state);
signal.ticket = Some(self.ticket(nerve));
let fabric = self.clone();
self.efferent().spawn(async move {
fabric.deliver(to, signal);
});
return;
}
}
}
fn full(nerve: &str) -> MotionError {
MotionError::failed(format!("the nerve '{nerve}' is full"))
}