use std::collections::{BTreeMap, HashMap};
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use tokio::sync::{mpsc, Notify};
use crate::conn::{Frame, FrameBody};
use crate::frame::FLAG_IGNORABLE;
use crate::link::{Outbox, DELIVER_DEPTH};
use crate::wire::FrameMessage;
use crate::Ava1Error;
pub type JobId = [u8; 16];
pub type BoxFut<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub fn is_data_type(ty: u8) -> bool {
(0x20..=0x3F).contains(&ty)
}
pub fn job_of(f: &Frame) -> Option<JobId> {
f.body.get(..16).and_then(|b| b.try_into().ok())
}
#[derive(Debug)]
pub enum Inbound {
Control(Frame),
Lane {
lane: u16,
frame: Frame,
},
LaneUp(u16),
LaneDown(u16),
Closed(String),
}
#[derive(Clone)]
pub struct ConnTx {
outbox: Outbox,
}
impl ConnTx {
pub(crate) fn new(outbox: Outbox) -> Self {
Self { outbox }
}
pub async fn send_raw(
&self,
ty: u8,
flags: u8,
channel: u32,
body: impl Into<FrameBody>,
) -> Result<(), Ava1Error> {
self.outbox.send_frame(ty, flags, channel, body).await
}
pub async fn send_raw_marked(
&self,
ty: u8,
flags: u8,
channel: u32,
body: impl Into<FrameBody>,
taken: Arc<AtomicBool>,
) -> Result<(), Ava1Error> {
self.outbox
.send_frame_marked(ty, flags, channel, body, Some(taken))
.await
}
pub fn writer_dead(&self) -> bool {
self.outbox.writer_dead()
}
pub async fn flush(&self, limit: std::time::Duration) -> bool {
self.outbox.flush(limit).await
}
pub async fn send<M: FrameMessage>(&self, m: &M) -> Result<(), Ava1Error> {
self.send_raw(M::TYPE, 0, 0, m.to_bytes()?).await
}
pub async fn send_ignorable<M: FrameMessage>(&self, m: &M) -> Result<(), Ava1Error> {
self.send_raw(M::TYPE, FLAG_IGNORABLE, 0, m.to_bytes()?)
.await
}
}
#[derive(Clone)]
pub struct LaneTx {
pub id: u16,
gen: u64,
pub tx: ConnTx,
closer: Arc<Notify>,
}
impl LaneTx {
pub fn close(&self) {
self.closer.notify_one();
}
}
pub trait LaneOpener: Send + Sync {
fn open(&self) -> BoxFut<'_, Result<u16, Ava1Error>>;
fn close(&self, id: u16);
}
pub trait JobHost: Send + Sync {
fn accept(&self, link: JobLink, first: Frame, peer: [u8; 32]);
}
struct JobInbox {
data: mpsc::Sender<Inbound>,
events: mpsc::UnboundedSender<Inbound>,
id: Arc<()>,
}
pub struct Inbox {
data: mpsc::Receiver<Inbound>,
events: mpsc::UnboundedReceiver<Inbound>,
id: Arc<()>,
events_done: bool,
}
impl Inbox {
pub async fn recv(&mut self) -> Option<Inbound> {
if let Ok(ev) = self.events.try_recv() {
return Some(ev);
}
loop {
if self.events_done {
return self.data.recv().await;
}
tokio::select! {
biased;
ev = self.events.recv() => match ev {
Some(i) => return Some(i),
None => self.events_done = true,
},
d = self.data.recv() => return d,
}
}
}
}
#[derive(Default)]
pub struct Router {
jobs: Mutex<HashMap<JobId, JobInbox>>,
lanes: Mutex<BTreeMap<u16, LaneTx>>,
next_gen: AtomicU64,
closed: Mutex<Option<String>>,
}
impl Router {
pub fn register(&self, job: JobId) -> Inbox {
let (data, rx) = mpsc::channel(DELIVER_DEPTH);
let (events, erx) = mpsc::unbounded_channel();
let lanes = self.lanes.lock().unwrap();
let closed = self.closed.lock().unwrap().clone();
if let Some(why) = closed {
let _ = events.send(Inbound::Closed(why));
} else {
for id in lanes.keys() {
let _ = events.send(Inbound::LaneUp(*id));
}
}
let id = Arc::new(());
let inbox = JobInbox {
data,
events,
id: id.clone(),
};
let replaced = self.jobs.lock().unwrap().insert(job, inbox);
drop(lanes);
if let Some(old) = replaced {
let _ = old.events.send(Inbound::Closed("superseded".into()));
}
Inbox {
data: rx,
events: erx,
id,
events_done: false,
}
}
pub fn unregister(&self, job: &JobId, id: &Arc<()>) {
let mut jobs = self.jobs.lock().unwrap();
if jobs.get(job).is_some_and(|i| Arc::ptr_eq(&i.id, id)) {
jobs.remove(job);
}
}
pub fn job_count(&self) -> usize {
self.jobs.lock().unwrap().len()
}
pub fn has_job(&self, job: &JobId) -> bool {
self.jobs.lock().unwrap().contains_key(job)
}
pub async fn route_control(&self, f: Frame) -> Option<Frame> {
let job = job_of(&f)?;
let Some(tx) = self.jobs.lock().unwrap().get(&job).map(|i| i.data.clone()) else {
return Some(f);
};
match tx.send(Inbound::Control(f)).await {
Ok(()) => None,
Err(e) => match e.0 {
Inbound::Control(f) => Some(f),
_ => None,
},
}
}
pub async fn route_lane(&self, lane: u16, f: Frame) {
if let Some(job) = job_of(&f) {
let tx = self.jobs.lock().unwrap().get(&job).map(|i| i.data.clone());
if let Some(tx) = tx {
let _ = tx.send(Inbound::Lane { lane, frame: f }).await;
}
}
}
pub(crate) fn lane_up(&self, id: u16, outbox: Outbox) -> u64 {
let gen = self.next_gen.fetch_add(1, Ordering::Relaxed) + 1;
let mut lanes = self.lanes.lock().unwrap();
let closed = self.closed.lock().unwrap().is_some();
if closed {
return gen;
}
lanes.insert(
id,
LaneTx {
id,
gen,
tx: ConnTx::new(outbox),
closer: Arc::default(),
},
);
self.broadcast(|| Inbound::LaneUp(id));
drop(lanes);
gen
}
pub(crate) fn lane_closer(&self, id: u16, gen: u64) -> Option<Arc<Notify>> {
self.lanes
.lock()
.unwrap()
.get(&id)
.filter(|t| t.gen == gen)
.map(|t| t.closer.clone())
}
pub(crate) fn lane_down(&self, id: u16, gen: u64) {
let mut l = self.lanes.lock().unwrap();
if l.get(&id).is_some_and(|t| t.gen == gen) {
l.remove(&id);
self.broadcast(|| Inbound::LaneDown(id));
}
}
pub fn lanes(&self) -> Vec<LaneTx> {
self.lanes.lock().unwrap().values().cloned().collect()
}
pub fn lane(&self, id: u16) -> Option<LaneTx> {
self.lanes.lock().unwrap().get(&id).cloned()
}
pub(crate) fn is_current(&self, l: &LaneTx) -> bool {
self.lanes
.lock()
.unwrap()
.get(&l.id)
.is_some_and(|t| t.gen == l.gen)
}
pub(crate) fn close(&self, why: &str) {
{
let mut c = self.closed.lock().unwrap();
if c.is_some() {
return;
}
*c = Some(why.to_string());
}
let mut lanes = self.lanes.lock().unwrap();
lanes.clear();
self.broadcast(|| Inbound::Closed(why.to_string()));
drop(lanes);
}
fn broadcast(&self, ev: impl Fn() -> Inbound) {
for inbox in self.jobs.lock().unwrap().values() {
let _ = inbox.events.send(ev());
}
}
}
pub struct JobLink {
pub job_id: JobId,
pub rx: Inbox,
pub control: ConnTx,
router: Arc<Router>,
opener: Option<Arc<dyn LaneOpener>>,
}
impl JobLink {
pub(crate) fn new(
job_id: JobId,
router: Arc<Router>,
control: ConnTx,
opener: Option<Arc<dyn LaneOpener>>,
) -> Self {
let rx = router.register(job_id);
Self {
job_id,
rx,
control,
router,
opener,
}
}
pub fn lanes(&self) -> Vec<LaneTx> {
self.router.lanes()
}
pub fn lane(&self, id: u16) -> Option<LaneTx> {
self.router.lane(id)
}
pub(crate) fn router(&self) -> Arc<Router> {
self.router.clone()
}
pub fn opener(&self) -> Option<&Arc<dyn LaneOpener>> {
self.opener.as_ref()
}
}
impl Drop for JobLink {
fn drop(&mut self) {
self.router.unregister(&self.job_id, &self.rx.id);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conn::{FrameReader, FrameWriter};
use crate::session::Timing;
use tokio::io::{duplex, split};
use tokio::time::{timeout, Duration};
fn frame_for(job: JobId) -> Frame {
let mut body = vec![0u8; 32];
body[..16].copy_from_slice(&job);
Frame {
ty: crate::gen::Chunk::TYPE,
flags: 0,
channel: 0,
body,
}
}
fn outbox() -> Outbox {
let (a, _b) = duplex(4096);
let (r, w) = split(a);
let (tx, _rx) = mpsc::channel(1);
let (_link, outbox) = crate::link::drive(
FrameReader::new(r),
FrameWriter::new(w),
Timing::default(),
tx,
);
outbox
}
#[tokio::test]
async fn closing_a_lane_wakes_the_task_that_runs_it_and_only_that_generation() {
let router = Arc::new(Router::default());
let gen = router.lane_up(3, outbox());
let closer = router.lane_closer(3, gen).expect("the lane is up");
assert!(
router.lane_closer(3, gen + 1).is_none(),
"another generation"
);
router.lane(3).expect("the lane is up").close();
timeout(Duration::from_secs(1), closer.notified())
.await
.expect("the lane's task was not told to close");
router.lane_down(3, gen);
assert!(router.lane_closer(3, gen).is_none(), "a lane that is down");
}
#[tokio::test]
async fn a_job_that_stops_reading_slows_the_router_instead_of_queueing_frames() {
let router = Router::default();
let mut inbox = router.register([7; 16]);
for _ in 0..DELIVER_DEPTH {
router.route_lane(1, frame_for([7; 16])).await;
}
assert!(
timeout(
Duration::from_millis(100),
router.route_lane(1, frame_for([7; 16]))
)
.await
.is_err(),
"the inbox held more than its bound"
);
assert!(matches!(inbox.recv().await, Some(Inbound::Lane { .. })));
assert!(
timeout(
Duration::from_secs(1),
router.route_lane(1, frame_for([7; 16]))
)
.await
.is_ok(),
"the router kept waiting after the job read"
);
}
#[tokio::test]
async fn a_full_inbox_still_hears_the_session_end() {
let router = Router::default();
let mut inbox = router.register([8; 16]);
for _ in 0..DELIVER_DEPTH {
router.route_lane(1, frame_for([8; 16])).await;
}
router.close("done");
let ev = timeout(Duration::from_secs(1), inbox.recv())
.await
.expect("no event within 1 s");
assert!(matches!(ev, Some(Inbound::Closed(why)) if why == "done"));
}
#[tokio::test]
async fn a_second_registration_ends_the_first_and_a_stale_link_cannot_take_the_id() {
let router = Router::default();
let mut first = router.register([9; 16]);
let mut second = router.register([9; 16]);
let ev = timeout(Duration::from_secs(1), first.recv())
.await
.expect("no event within 1 s");
assert!(
matches!(ev, Some(Inbound::Closed(_))),
"the replaced job is told why"
);
assert!(first.recv().await.is_none(), "and then its channel ends");
let stale = first.id.clone();
router.unregister(&[9; 16], &stale);
assert!(router.has_job(&[9; 16]), "a stale link took the id away");
router.route_lane(1, frame_for([9; 16])).await;
assert!(matches!(second.recv().await, Some(Inbound::Lane { .. })));
}
#[tokio::test]
async fn a_lane_that_comes_up_as_the_session_ends_is_not_reported() {
let router = Router::default();
let mut inbox = router.register([10; 16]);
router.close("done");
let ev = timeout(Duration::from_secs(1), inbox.recv())
.await
.expect("no event within 1 s");
assert!(matches!(ev, Some(Inbound::Closed(_))));
router.lane_up(3, outbox());
assert!(router.lanes().is_empty(), "a closed router has no lanes");
assert!(
timeout(Duration::from_millis(100), inbox.recv())
.await
.is_err(),
"a lane came up after the session ended"
);
}
#[tokio::test]
async fn a_registration_that_races_close_still_hears_it() {
let router = Arc::new(Router::default());
let held = router.lanes.lock().unwrap();
let r = router.clone();
let t = std::thread::spawn(move || r.register([11; 16]));
std::thread::sleep(Duration::from_millis(50));
*router.closed.lock().unwrap() = Some("done".into());
drop(held);
let mut inbox = t.join().unwrap();
let ev = timeout(Duration::from_secs(1), inbox.recv())
.await
.expect("a closed router told the new job");
assert!(matches!(ev, Some(Inbound::Closed(why)) if why == "done"));
}
#[tokio::test]
async fn a_lane_that_lands_while_the_session_is_closing_is_not_kept() {
let router = Arc::new(Router::default());
let ob = outbox();
let held = router.lanes.lock().unwrap();
let r = router.clone();
let t = std::thread::spawn(move || r.lane_up(3, ob));
std::thread::sleep(Duration::from_millis(50));
*router.closed.lock().unwrap() = Some("done".into());
drop(held);
t.join().unwrap();
assert!(router.lanes().is_empty(), "a closed router kept a lane");
}
}