Skip to main content

orbit_stream/
lib.rs

1//! Bounded byte streams between two members of an Orbit fleet.
2//!
3//! A stream is a place with two ends. [`Streams::create`] takes a slot in
4//! this process's lane, hands back the endpoint for side A and a [`Ticket`]
5//! for side B; whoever in the fleet [`Streams::open`]s that ticket holds the
6//! other end. Each direction is its own bounded ring: bytes go in at one end
7//! and come out, in order and without loss, at the other; when the ring is
8//! full the writer waits, when it is empty the reader waits. A writer that
9//! is done shuts its direction down (FIN) and the reader drains to a clean
10//! end; a writer that gives up resets it and the reader sees an error.
11//!
12//! Standalone the rings live in process memory; in a shared-memory fleet
13//! they live in the fleet's segment and the two ends may be in different
14//! processes. The address that travels is a [`NetId64`]: its kind is the
15//! segment's, its node the creator's lane, its counter the slot and the
16//! slot's generation; the [`Ticket`] around it adds the table's epoch. An
17//! address kept past the stream's end, or past a reset, answers
18//! [`Error::Stale`] instead of reaching whoever took the slot next.
19//!
20//! The blocking calls park on the direction's own change word through the
21//! platform's shared address wait, like a cell. With the `tokio` feature the
22//! halves implement `AsyncRead` and `AsyncWrite`; a poll that finds nothing
23//! registers its waker and returns `Pending`, and one thread per process
24//! turns the fleet's doorbell into those wakes. No thread and no descriptor
25//! per stream.
26
27use std::fmt;
28use std::io;
29use std::str::FromStr;
30use std::sync::atomic::{AtomicU32, Ordering};
31use std::time::{Duration, Instant};
32use std::sync::{Arc, Mutex, MutexGuard};
33
34use bytes::{Bytes, BytesMut};
35use orbit_core::{Fleet, NetId64};
36
37mod layout;
38#[cfg(feature = "tokio")]
39mod poll;
40mod table;
41mod wake;
42
43use layout::{
44    Direction, FLAG_FIN, FLAG_READER_GONE, FLAG_RESET, GENERATION_MASK, SIDE_CLAIMED, SIDE_FREE,
45    SIDE_RELEASED, SLOT_BITS, SLOT_EMPTY, SLOT_LIVE, SLOT_MASK, Slot,
46};
47use orbit_core::NodeId;
48use table::Table;
49#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
50pub use orbit_core::readiness::Readiness;
51pub use table::{segment_size, segment_size_for};
52use wake::Interest;
53
54/// Reserved Orbit SHM kind for the default stream segment. Another table
55/// names its own through a [`StreamSpec`].
56pub const STREAM_KIND: u8 = 246;
57/// Streams one fleet node can hold open at once, in the default spec.
58///
59/// Compile-time geometry: `ORBIT_STREAM_LANE_CAPACITY` in the application's
60/// `.cargo/config.toml` overrides the default. A power of two, at most
61/// 65 536. It is the *default* table's value; a [`StreamSpec`] gives
62/// another table another one. Peers opening the same kind with a different
63/// value are refused at the segment.
64pub const STREAM_LANE_CAPACITY: usize =
65    orbit_core::compile::usize_from_env(option_env!("ORBIT_STREAM_LANE_CAPACITY"), 256);
66/// Bytes each direction of a stream can hold before its writer waits, in
67/// the default spec.
68///
69/// Compile-time geometry: `ORBIT_STREAM_BUFFER_BYTES`. A power of two. Part
70/// of the same wire contract as the lane capacity, and like it a per-table
71/// choice through [`StreamSpec`].
72pub const STREAM_BUFFER_BYTES: usize =
73    orbit_core::compile::usize_from_env(option_env!("ORBIT_STREAM_BUFFER_BYTES"), 64 * 1024);
74
75/// Which segment a [`Streams`] table uses, and how big its lanes and
76/// rings are.
77///
78/// One fleet can hold several independent stream tables. They are not
79/// interchangeable: a relay carrying response bodies wants a ring sized to
80/// a body, while a control channel carrying short frames is faster with a
81/// small one, and a lane is a ceiling on concurrent streams per node. Each
82/// names its own kind, and a kind is a fleet-wide identity — every process
83/// opening it must pass the same geometry, and the segment's header
84/// refuses a peer that does not. [`StreamSpec::DEFAULT`] is what
85/// [`Streams::new`] opens; its values are the compile-time geometry.
86#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
87pub struct StreamSpec {
88    /// The Orbit SHM kind, and with it the segment name.
89    pub kind: u8,
90    /// Streams one fleet node can hold open at once. A power of two, at
91    /// most 65 536.
92    pub lane_capacity: usize,
93    /// Bytes each direction holds before its writer waits. A power of two.
94    pub buffer_bytes: usize,
95}
96
97impl StreamSpec {
98    pub const DEFAULT: Self = Self::new(STREAM_KIND, STREAM_LANE_CAPACITY, STREAM_BUFFER_BYTES);
99
100    pub const fn new(kind: u8, lane_capacity: usize, buffer_bytes: usize) -> Self {
101        Self { kind, lane_capacity, buffer_bytes }
102    }
103
104    /// The segment one node's streams occupy at most: every slot's two
105    /// rings. Memory is backed as it is touched, but this is the ceiling
106    /// a deployment is choosing when it picks a lane and a ring.
107    pub const fn lane_bytes(&self) -> usize {
108        self.lane_capacity * 2 * self.buffer_bytes
109    }
110
111    /// What the compile-time geometry used to assert. A spec is checked
112    /// once, when its table is opened.
113    fn validate(self) -> Result<()> {
114        if self.lane_capacity == 0
115            || self.buffer_bytes == 0
116            || !self.lane_capacity.is_power_of_two()
117            || !self.buffer_bytes.is_power_of_two()
118            || self.lane_capacity > 1 << SLOT_BITS
119            || self.buffer_bytes > u32::MAX as usize
120        {
121            return Err(Error::Malformed(format!(
122                "stream spec kind={} lane_capacity={} buffer_bytes={}: both are powers of two, a lane holds at most {} streams and a ring at most {} bytes",
123                self.kind,
124                self.lane_capacity,
125                self.buffer_bytes,
126                1_usize << SLOT_BITS,
127                u32::MAX
128            )));
129        }
130        Ok(())
131    }
132}
133
134impl Default for StreamSpec {
135    fn default() -> Self {
136        Self::DEFAULT
137    }
138}
139
140pub type Result<T, E = Error> = std::result::Result<T, E>;
141
142/// Additions are expected: a cause discovered later lands here rather
143/// than in a new major version, so a caller matches what it handles and
144/// leaves the rest to a catch-all.
145#[non_exhaustive]
146#[derive(Debug)]
147pub enum Error {
148    /// The address names a slot nothing occupies, or a generation that ended.
149    Stale(StreamId),
150    /// That side of the stream is already held, or was held and released.
151    AlreadyClaimed(Ticket),
152    /// Every slot in this process's lane is in use.
153    Full {
154        capacity: usize,
155    },
156    /// The text or id is not a stream address.
157    Malformed(String),
158    /// This write half was shut down; nothing more goes in.
159    Closed,
160    /// The other end reset the direction; buffered bytes were discarded.
161    Reset,
162    /// The other end's reader is gone; nothing written here will be read.
163    PeerGone,
164    /// Nothing to read, or no room to write, right now.
165    WouldBlock,
166    Io(io::Error),
167}
168
169impl fmt::Display for Error {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        match self {
172            Self::Stale(id) => write!(f, "stream {id} has ended"),
173            Self::AlreadyClaimed(ticket) => write!(f, "stream side {ticket} is already held"),
174            Self::Full { capacity } => write!(f, "stream lane is full: capacity={capacity}"),
175            Self::Malformed(text) => write!(f, "not a stream address: {text:?}"),
176            Self::Closed => f.write_str("stream write half is shut down"),
177            Self::Reset => f.write_str("stream direction was reset by the peer"),
178            Self::PeerGone => f.write_str("stream peer reader is gone"),
179            Self::WouldBlock => f.write_str("stream operation would block"),
180            Self::Io(error) => write!(f, "Orbit stream io error: {error}"),
181        }
182    }
183}
184
185impl std::error::Error for Error {
186    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
187        match self {
188            Self::Io(error) => Some(error),
189            _ => None,
190        }
191    }
192}
193
194impl From<io::Error> for Error {
195    fn from(value: io::Error) -> Self {
196        Self::Io(value)
197    }
198}
199
200impl From<Error> for io::Error {
201    fn from(value: Error) -> Self {
202        let kind = match &value {
203            Error::Stale(_) | Error::AlreadyClaimed(_) => io::ErrorKind::NotConnected,
204            Error::Full { .. } => io::ErrorKind::OutOfMemory,
205            Error::Malformed(_) => io::ErrorKind::InvalidInput,
206            Error::Closed => io::ErrorKind::BrokenPipe,
207            Error::Reset => io::ErrorKind::ConnectionReset,
208            Error::PeerGone => io::ErrorKind::BrokenPipe,
209            Error::WouldBlock => io::ErrorKind::WouldBlock,
210            Error::Io(error) => error.kind(),
211        };
212        io::Error::new(kind, value)
213    }
214}
215
216/// The address of one stream: a [`NetId64`] whose node is the creator's
217/// lane and whose counter is the slot and its generation.
218#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
219pub struct StreamId(NetId64);
220
221impl StreamId {
222    pub const fn from_net_id(id: NetId64) -> Self {
223        Self(id)
224    }
225
226    pub const fn net_id(self) -> NetId64 {
227        self.0
228    }
229
230    pub const fn kind(self) -> u8 {
231        self.0.kind()
232    }
233
234    /// The lane: the fleet node that created the stream.
235    pub const fn node(self) -> u16 {
236        self.0.node()
237    }
238
239    pub const fn slot(self) -> u32 {
240        (self.0.counter() & SLOT_MASK) as u32
241    }
242
243    pub const fn generation(self) -> u32 {
244        (self.0.counter() >> SLOT_BITS) as u32
245    }
246
247    fn make(kind: u8, node: u16, slot: u32, generation: u32) -> Self {
248        Self(NetId64::make(
249            kind,
250            node,
251            ((generation as u64) << SLOT_BITS) | (slot as u64 & SLOT_MASK),
252        ))
253    }
254}
255
256impl fmt::Display for StreamId {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        self.0.fmt(f)
259    }
260}
261
262impl FromStr for StreamId {
263    type Err = Error;
264
265    fn from_str(text: &str) -> Result<Self> {
266        text.parse::<NetId64>()
267            .map(Self)
268            .map_err(|_| Error::Malformed(text.to_owned()))
269    }
270}
271
272/// Which end of a stream a handle holds. The creator holds `A`.
273#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
274pub enum Side {
275    A,
276    B,
277}
278
279impl Side {
280    const fn index(self) -> usize {
281        match self {
282            Self::A => 0,
283            Self::B => 1,
284        }
285    }
286
287    /// The direction this side writes into.
288    const fn write_direction(self) -> usize {
289        self.index()
290    }
291
292    /// The direction this side reads from.
293    const fn read_direction(self) -> usize {
294        1 - self.index()
295    }
296
297    const fn letter(self) -> char {
298        match self {
299            Self::A => 'a',
300            Self::B => 'b',
301        }
302    }
303}
304
305/// One end of a stream, in the form that crosses a setup channel: the
306/// address, the side, and the epoch of the table the address was minted
307/// in. Prints as `<address>/b/<epoch>`.
308///
309/// Two things keep a ticket from reaching the wrong memory: the slot
310/// generation inside the address separates reuses of a slot within one
311/// epoch, and the epoch separates before and after a quiescent reset or a
312/// recreated segment, which reinstalls slots from generation one.
313#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
314pub struct Ticket {
315    pub id: StreamId,
316    pub side: Side,
317    pub epoch: u64,
318}
319
320impl fmt::Display for Ticket {
321    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322        write!(f, "{}/{}/{}", self.id, self.side.letter(), self.epoch)
323    }
324}
325
326impl FromStr for Ticket {
327    type Err = Error;
328
329    fn from_str(text: &str) -> Result<Self> {
330        let malformed = || Error::Malformed(text.to_owned());
331        let (rest, epoch) = text.rsplit_once('/').ok_or_else(malformed)?;
332        let (id, side) = rest.rsplit_once('/').ok_or_else(malformed)?;
333        let side = match side {
334            "a" => Side::A,
335            "b" => Side::B,
336            _ => return Err(malformed()),
337        };
338        Ok(Self {
339            id: id.parse().map_err(|_| malformed())?,
340            side,
341            epoch: epoch.parse().map_err(|_| malformed())?,
342        })
343    }
344}
345
346/// Which life of a process holds a side. A node id is a role that a
347/// replacement process inherits; the incarnation tells the two apart, so a
348/// late death report for the old process never touches the new one's
349/// streams. Orbit does not mint it: the embedder knows what identifies one
350/// live process (a start stamp, a supervisor generation) and supplies it.
351#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
352pub struct Incarnation(u64);
353
354impl Incarnation {
355    pub const fn new(value: u64) -> Self {
356        Self(value)
357    }
358
359    pub const fn get(self) -> u64 {
360        self.0
361    }
362}
363
364/// The fleet's stream table. Cheap to clone; every clone in a process is
365/// the same table, the same driver and the same wakers.
366#[derive(Clone)]
367pub struct Streams {
368    table: Arc<Table>,
369}
370
371impl Streams {
372    /// Open the fleet's table as this process's incarnation. Every side
373    /// this process claims is stamped with it, and [`Streams::node_dead`]
374    /// for the same node and incarnation is what ends those sides.
375    pub fn new(fleet: Arc<Fleet>, incarnation: Incarnation) -> Result<Self> {
376        Self::with_spec(fleet, incarnation, StreamSpec::DEFAULT)
377    }
378
379    /// Open the table `spec` names. Independent specs are independent
380    /// tables: separate segments, separate lanes and rings, separate
381    /// epochs, and a `reset_all` on one leaves the others alone. A process
382    /// may hold as many as it has specs, but each under one incarnation.
383    pub fn with_spec(
384        fleet: Arc<Fleet>,
385        incarnation: Incarnation,
386        spec: StreamSpec,
387    ) -> Result<Self> {
388        spec.validate()?;
389        Ok(Self {
390            table: table::open(&fleet, incarnation, spec)?,
391        })
392    }
393
394    /// The kind this table's segment lives under.
395    pub fn kind(&self) -> u8 {
396        self.table.kind()
397    }
398
399    /// A descriptor that becomes readable when something among this
400    /// process's streams may have changed, for a runtime that parks on
401    /// descriptors rather than on wakers.
402    ///
403    /// Edge-triggered and coalescing: drain it, then re-try the
404    /// non-blocking calls on the streams you hold. It is what makes this
405    /// table usable from a foreign event loop — an embedded runtime, a
406    /// reactor of its own — without a thread or a descriptor per stream.
407    ///
408    /// One per table: a second caller is refused rather than handed a
409    /// descriptor whose signals the first would drain.
410    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
411    pub fn readiness(&self) -> Result<Readiness> {
412        self.table.take_readiness()
413    }
414
415    /// The epoch every ticket minted from this table carries right now.
416    pub fn epoch(&self) -> u64 {
417        self.table.epoch()
418    }
419
420    /// Take a slot in this process's lane. The endpoint is side A; the
421    /// ticket names side B and is what the other end opens.
422    pub fn create(&self) -> Result<(Endpoint, Ticket)> {
423        let (index, generation) = self.table.allocate()?;
424        let id = StreamId::make(
425            self.table.kind(),
426            self.table.node(),
427            (index % self.table.geometry().lane_capacity) as u32,
428            generation,
429        );
430        let handle = Arc::new(Handle {
431            table: Arc::clone(&self.table),
432            index,
433            id,
434            side: Side::A,
435        });
436        Ok((
437            Endpoint::new(handle),
438            Ticket {
439                id,
440                side: Side::B,
441                epoch: self.table.epoch(),
442            },
443        ))
444    }
445
446    /// Hold the side a ticket names. Each side can be held once per stream.
447    pub fn open(&self, ticket: Ticket) -> Result<Endpoint> {
448        let index = self.locate(ticket.id)?;
449        if ticket.epoch != self.table.epoch() {
450            return Err(Error::Stale(ticket.id));
451        }
452        let slot = &self.table.slots()[index];
453        if !slot.is(ticket.id.generation()) {
454            return Err(Error::Stale(ticket.id));
455        }
456        let claimed = &slot.claimed[ticket.side.index()];
457        if claimed
458            .compare_exchange(SIDE_FREE, SIDE_CLAIMED, Ordering::SeqCst, Ordering::SeqCst)
459            .is_err()
460        {
461            return Err(Error::AlreadyClaimed(ticket));
462        }
463        // The slot may have ended between the check and the claim; give the
464        // claim back rather than hold a side of whatever comes next.
465        if !slot.is(ticket.id.generation()) {
466            let _ = claimed.compare_exchange(
467                SIDE_CLAIMED,
468                SIDE_FREE,
469                Ordering::SeqCst,
470                Ordering::SeqCst,
471            );
472            return Err(Error::Stale(ticket.id));
473        }
474        slot.incarnation[ticket.side.index()].store(self.table.incarnation(), Ordering::Release);
475        slot.node[ticket.side.index()].store(self.table.node(), Ordering::Release);
476        // Whoever was waiting for this side to show up.
477        for direction in &slot.directions {
478            self.table.notify(index, slot, direction, true);
479        }
480        Ok(Endpoint::new(Arc::new(Handle {
481            table: Arc::clone(&self.table),
482            index,
483            id: ticket.id,
484            side: ticket.side,
485        })))
486    }
487
488    /// Whether the address names a live stream right now.
489    pub fn is_live(&self, id: StreamId) -> bool {
490        self.locate(id)
491            .map(|index| self.table.slots()[index].is(id.generation()))
492            .unwrap_or(false)
493    }
494
495    /// Tell `node`'s process that side B of this stream is its to take:
496    /// the slot is put in that node's offer bitmap and its doorbell rung.
497    /// Discovery only; the ticket may just as well travel by any other
498    /// channel. An offer is seen once, by that node, through
499    /// [`Streams::take_offer`] and its waiting forms.
500    pub fn offer(&self, ticket: Ticket, to: NodeId) -> Result<()> {
501        let index = self.locate(ticket.id)?;
502        if ticket.epoch != self.table.epoch()
503            || !self.table.slots()[index].is(ticket.id.generation())
504        {
505            return Err(Error::Stale(ticket.id));
506        }
507        self.table.offer(index, to.get())
508    }
509
510    /// The next stream offered to this node, if any. A stream that ended
511    /// between the offer and now is skipped.
512    pub fn take_offer(&self) -> Option<Ticket> {
513        while let Some(index) = self.table.take_offer() {
514            if let Some(ticket) = self.ticket_for(index) {
515                return Some(ticket);
516            }
517        }
518        None
519    }
520
521    /// The same wait, bounded: `None` is the timeout and nothing else.
522    /// A worker whose shutdown must be observed waits this way rather
523    /// than parking on the next request forever.
524    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
525    pub fn take_offer_timeout(&self, timeout: Duration) -> Result<Option<Ticket>> {
526        let deadline = Instant::now() + timeout;
527        loop {
528            if let Some(ticket) = self.take_offer() {
529                return Ok(Some(ticket));
530            }
531            let left = deadline.saturating_duration_since(Instant::now());
532            if left.is_zero() {
533                return Ok(self.take_offer());
534            }
535            if !self.table.wait_offer_timeout(left)? {
536                return Ok(self.take_offer());
537            }
538        }
539    }
540
541    /// Park the thread until a stream is offered to this node.
542    pub fn blocking_take_offer(&self) -> Result<Ticket> {
543        loop {
544            if let Some(ticket) = self.take_offer() {
545                return Ok(ticket);
546            }
547            self.table.wait_offer()?;
548        }
549    }
550
551    /// Readiness for a task: the next offer, or the waker is registered
552    /// and `Pending` comes back.
553    pub fn poll_take_offer(
554        &self,
555        cx: &mut std::task::Context<'_>,
556    ) -> std::task::Poll<Result<Ticket>> {
557        if let Some(ticket) = self.take_offer() {
558            return std::task::Poll::Ready(Ok(ticket));
559        }
560        if let Err(error) = self.table.register_offer(cx.waker()) {
561            return std::task::Poll::Ready(Err(error));
562        }
563        match self.take_offer() {
564            Some(ticket) => std::task::Poll::Ready(Ok(ticket)),
565            None => std::task::Poll::Pending,
566        }
567    }
568
569    /// Every process generation that still holds a side of a stream here.
570    ///
571    /// The mirror of `Pool::owners`, and for the same caller: a supervisor
572    /// that has to decide what to report dead after losing its own record.
573    /// It says who is *in* the table, never who is alive.
574    pub fn owners(&self) -> Vec<(NodeId, Incarnation)> {
575        let mut seen: Vec<(NodeId, Incarnation)> = Vec::new();
576        for slot in self.table.slots() {
577            if slot.state.load(Ordering::Acquire) != SLOT_LIVE {
578                continue;
579            }
580            for side in 0..2 {
581                if slot.claimed[side].load(Ordering::Acquire) != SIDE_CLAIMED {
582                    continue;
583                }
584                let holder = (
585                    NodeId::new(slot.node[side].load(Ordering::Acquire)),
586                    Incarnation::new(slot.incarnation[side].load(Ordering::Acquire)),
587                );
588                if !seen.contains(&holder) {
589                    seen.push(holder);
590                }
591            }
592        }
593        seen.sort_by_key(|(node, incarnation)| (node.get(), incarnation.get()));
594        seen
595    }
596
597    /// A confirmed death, reported by whoever supervises processes: every
598    /// side that incarnation of `node` held, in any lane, is finished. Its
599    /// unfinished writes become resets and its reads are gone, so a holder
600    /// of the other side wakes with an error; that holder keeps the slot
601    /// until it drops its handles, and only then is the slot reused. A
602    /// report for another incarnation of the same node touches nothing.
603    pub fn node_dead(&self, node: NodeId, incarnation: Incarnation) {
604        self.table.node_dead(node.get(), incarnation.get());
605    }
606
607    /// Clear the table during quiescent owner boot and start a new epoch.
608    /// Every handle and every ticket from before goes stale.
609    pub fn reset_all(&self) {
610        self.table.reset_all();
611    }
612
613    fn ticket_for(&self, index: usize) -> Option<Ticket> {
614        let slot = &self.table.slots()[index];
615        let generation = slot.generation.load(Ordering::Acquire);
616        if !slot.is(generation) {
617            return None;
618        }
619        let lane_capacity = self.table.geometry().lane_capacity;
620        let id = StreamId::make(
621            self.table.kind(),
622            (index / lane_capacity) as u16,
623            (index % lane_capacity) as u32,
624            generation,
625        );
626        Some(Ticket {
627            id,
628            side: Side::B,
629            epoch: self.table.epoch(),
630        })
631    }
632
633    /// Remove the SHM object. Existing mappings stay valid until their
634    /// processes release them.
635    #[cfg(unix)]
636    pub fn unlink(&self) -> Result<()> {
637        self.table.unlink()
638    }
639
640    fn locate(&self, id: StreamId) -> Result<usize> {
641        let geometry = self.table.geometry();
642        if id.kind() != self.table.kind()
643            || usize::from(id.node()) >= geometry.fleet_capacity
644            || id.slot() as usize >= geometry.lane_capacity
645            || id.generation() == 0
646            || id.generation() > GENERATION_MASK
647        {
648            return Err(Error::Malformed(id.to_string()));
649        }
650        Ok(usize::from(id.node()) * geometry.lane_capacity + id.slot() as usize)
651    }
652}
653
654/// One held side. Dropped when both halves are gone; that releases the side.
655struct Handle {
656    table: Arc<Table>,
657    index: usize,
658    id: StreamId,
659    side: Side,
660}
661
662impl Handle {
663    fn slot(&self) -> Result<&Slot> {
664        let slot = &self.table.slots()[self.index];
665        if slot.is(self.id.generation()) {
666            Ok(slot)
667        } else {
668            Err(Error::Stale(self.id))
669        }
670    }
671
672    fn direction(slot: &Slot, direction: usize) -> &Direction {
673        &slot.directions[direction]
674    }
675
676    /// One attempt at the direction this side writes into.
677    fn try_write(&self, buf: &[u8]) -> Result<usize> {
678        let slot = self.slot()?;
679        let direction = Self::direction(slot, self.side.write_direction());
680        let flags = direction.flags();
681        if flags & FLAG_RESET != 0 {
682            return Err(Error::Reset);
683        }
684        if flags & FLAG_FIN != 0 {
685            return Err(Error::Closed);
686        }
687        if flags & FLAG_READER_GONE != 0 {
688            return Err(Error::PeerGone);
689        }
690        let buffer_bytes = self.table.geometry().buffer_bytes;
691        let head = direction.head.load(Ordering::Relaxed);
692        let tail = direction.tail.load(Ordering::Acquire);
693        let free = buffer_bytes - (head - tail) as usize;
694        if buf.is_empty() {
695            return Ok(0);
696        }
697        if free == 0 {
698            return Err(Error::WouldBlock);
699        }
700        let len = buf.len().min(free);
701        let base = self.table.buffer(self.index, self.side.write_direction());
702        let start = (head as usize) & (buffer_bytes - 1);
703        let first = len.min(buffer_bytes - start);
704        // SAFETY: only this side writes this direction, and `[head, head+len)`
705        // is free space the reader will not touch until `head` moves.
706        unsafe {
707            std::ptr::copy_nonoverlapping(buf.as_ptr(), base.add(start), first);
708            std::ptr::copy_nonoverlapping(buf.as_ptr().add(first), base, len - first);
709        }
710        direction.head.store(head + len as u64, Ordering::SeqCst);
711        // A reader parks only on an empty ring: it looks, registers, looks
712        // again. Whether one could be parked is the tail *after* this
713        // commit, never the one read before the copy — the reader may have
714        // drained the ring and parked while we were copying into it, and
715        // that older tail would say "nobody to wake" about a task asleep on
716        // these very bytes. Each side orders its own store before the
717        // other's load, so at least one of the two sees the other: the
718        // reader sees the new head and does not park, or this sees the
719        // drained tail and rings.
720        std::sync::atomic::fence(Ordering::SeqCst);
721        let drained = direction.tail.load(Ordering::SeqCst) >= head;
722        self.table.notify(self.index, slot, direction, drained);
723        Ok(len)
724    }
725
726    /// One attempt at the direction this side reads from. `Ok(0)` with a
727    /// non-empty buffer is clean end of stream.
728    fn try_read(&self, buf: &mut [u8]) -> Result<usize> {
729        let slot = self.slot()?;
730        let direction = Self::direction(slot, self.side.read_direction());
731        let flags = direction.flags();
732        if flags & FLAG_RESET != 0 {
733            return Err(Error::Reset);
734        }
735        let head = direction.head.load(Ordering::Acquire);
736        let tail = direction.tail.load(Ordering::Relaxed);
737        let available = (head - tail) as usize;
738        if buf.is_empty() {
739            return Ok(0);
740        }
741        if available == 0 {
742            if flags & FLAG_FIN != 0 {
743                return Ok(0);
744            }
745            return Err(Error::WouldBlock);
746        }
747        let len = buf.len().min(available);
748        let base = self.table.buffer(self.index, self.side.read_direction());
749        let buffer_bytes = self.table.geometry().buffer_bytes;
750        let start = (tail as usize) & (buffer_bytes - 1);
751        let first = len.min(buffer_bytes - start);
752        // SAFETY: `[tail, head)` was published by the writer's `Release`
753        // store of `head`, acquired above, and is not rewritten until `tail`
754        // moves past it.
755        unsafe {
756            std::ptr::copy_nonoverlapping(base.add(start), buf.as_mut_ptr(), first);
757            std::ptr::copy_nonoverlapping(base, buf.as_mut_ptr().add(first), len - first);
758        }
759        direction.tail.store(tail + len as u64, Ordering::SeqCst);
760        // The mirror image, and the other half of that agreement: a writer
761        // parks only on a full ring, so the head read after this consume is
762        // what says whether one is parked on the space just freed.
763        std::sync::atomic::fence(Ordering::SeqCst);
764        let head_now = direction.head.load(Ordering::SeqCst);
765        let was_full = head_now - tail >= buffer_bytes as u64;
766        self.table.notify(self.index, slot, direction, was_full);
767        Ok(len)
768    }
769
770    fn set_flag(&self, direction_index: usize, flag: u8) {
771        if let Ok(slot) = self.slot() {
772            let direction = Self::direction(slot, direction_index);
773            if direction.flags.fetch_or(flag, Ordering::SeqCst) & flag == 0 {
774                self.table.notify(self.index, slot, direction, true);
775            }
776        }
777    }
778
779    /// Park until `ready` holds for the direction, or the stream ends.
780    /// The same wait, bounded: `false` is the timeout and nothing else.
781    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
782    fn wait_until_timeout(
783        &self,
784        direction_index: usize,
785        ready: impl Fn(&Direction) -> bool,
786        timeout: Duration,
787    ) -> Result<bool> {
788        let deadline = Instant::now() + timeout;
789        loop {
790            let slot = self.slot()?;
791            let direction = Self::direction(slot, direction_index);
792            if ready(direction) {
793                return Ok(true);
794            }
795            let left = deadline.saturating_duration_since(Instant::now());
796            if left.is_zero() {
797                return Ok(false);
798            }
799            direction.waiters.fetch_add(1, Ordering::SeqCst);
800            let since = direction.changes.load(Ordering::SeqCst);
801            let outcome = if ready(direction) {
802                Ok(true)
803            } else {
804                wait_on_timeout(&direction.changes, since, left)
805            };
806            direction.waiters.fetch_sub(1, Ordering::SeqCst);
807            if !outcome? {
808                // The deadline passed; one last look, since readiness may
809                // have landed between the wait giving up and this line.
810                let slot = self.slot()?;
811                return Ok(ready(Self::direction(slot, direction_index)));
812            }
813        }
814    }
815
816    fn wait_until(&self, direction_index: usize, ready: impl Fn(&Direction) -> bool) -> Result<()> {
817        loop {
818            let slot = self.slot()?;
819            let direction = Self::direction(slot, direction_index);
820            if ready(direction) {
821                return Ok(());
822            }
823            direction.waiters.fetch_add(1, Ordering::SeqCst);
824            let since = direction.changes.load(Ordering::SeqCst);
825            let outcome = if ready(direction) {
826                Ok(())
827            } else {
828                wait_on(&direction.changes, since)
829            };
830            direction.waiters.fetch_sub(1, Ordering::SeqCst);
831            outcome?;
832        }
833    }
834
835    /// Check, register, check again: a change between the first look and
836    /// the registration is not missed.
837    fn poll_ready(
838        &self,
839        interest: Interest,
840        ready: impl Fn(&Direction) -> bool,
841        cx: &mut std::task::Context<'_>,
842    ) -> std::task::Poll<Result<()>> {
843        let slot = match self.slot() {
844            Ok(slot) => slot,
845            Err(error) => return std::task::Poll::Ready(Err(error)),
846        };
847        let direction = Self::direction(slot, interest.direction);
848        if ready(direction) {
849            return std::task::Poll::Ready(Ok(()));
850        }
851        if let Err(error) = self.table.register(self.index, interest, cx.waker()) {
852            return std::task::Poll::Ready(Err(error));
853        }
854        if ready(direction) {
855            std::task::Poll::Ready(Ok(()))
856        } else {
857            std::task::Poll::Pending
858        }
859    }
860}
861
862fn readable(direction: &Direction) -> bool {
863    direction.head.load(Ordering::Acquire) != direction.tail.load(Ordering::Relaxed)
864        || direction.flags() & (FLAG_FIN | FLAG_RESET) != 0
865}
866
867fn writable(direction: &Direction, buffer_bytes: usize) -> bool {
868    let queued =
869        (direction.head.load(Ordering::Relaxed) - direction.tail.load(Ordering::Acquire)) as usize;
870    queued < buffer_bytes
871        || direction.flags() & (FLAG_FIN | FLAG_RESET | FLAG_READER_GONE) != 0
872}
873
874impl Drop for Handle {
875    /// Both halves are gone: give the side back. The slot empties once no
876    /// side holds it any more, and every address to this generation goes
877    /// stale.
878    fn drop(&mut self) {
879        let slot = &self.table.slots()[self.index];
880        if !slot.is(self.id.generation()) {
881            return;
882        }
883        // Only a side that is still ours is ours to release: a death
884        // report may have finished it already.
885        if slot.claimed[self.side.index()]
886            .compare_exchange(
887                SIDE_CLAIMED,
888                SIDE_RELEASED,
889                Ordering::SeqCst,
890                Ordering::SeqCst,
891            )
892            .is_err()
893        {
894            return;
895        }
896        // Side A is claimed at creation, so a free B was simply never
897        // taken; a released or dead side is gone. Only a claimed side keeps
898        // the slot alive.
899        let other = slot.claimed[1 - self.side.index()].load(Ordering::SeqCst);
900        if other != SIDE_CLAIMED {
901            let _ = slot.state.compare_exchange(
902                SLOT_LIVE,
903                SLOT_EMPTY,
904                Ordering::SeqCst,
905                Ordering::SeqCst,
906            );
907            for direction in &slot.directions {
908                // A parked waiter with a handle to this generation wakes
909                // and finds it stale.
910                direction.changes.fetch_add(1, Ordering::SeqCst);
911                if direction.waiters.load(Ordering::SeqCst) > 0 {
912                    wake_on(&direction.changes);
913                }
914            }
915            self.table.registry.wake(self.index);
916        }
917    }
918}
919
920/// Both ends of one side: what [`Streams::create`] and [`Streams::open`]
921/// return. Reads and writes go through it directly, or [`Endpoint::split`]
922/// gives the halves to two tasks.
923pub struct Endpoint {
924    read: ReadHalf,
925    write: WriteHalf,
926}
927
928impl Endpoint {
929    fn new(handle: Arc<Handle>) -> Self {
930        Self {
931            read: ReadHalf {
932                handle: Arc::clone(&handle),
933            },
934            write: WriteHalf {
935                handle,
936                shut: false,
937            },
938        }
939    }
940
941    pub fn id(&self) -> StreamId {
942        self.read.handle.id
943    }
944
945    pub fn side(&self) -> Side {
946        self.read.handle.side
947    }
948
949    pub fn split(self) -> (ReadHalf, WriteHalf) {
950        (self.read, self.write)
951    }
952
953    pub fn try_read(&self, buf: &mut [u8]) -> Result<usize> {
954        self.read.try_read(buf)
955    }
956
957    pub fn blocking_read(&self, buf: &mut [u8]) -> Result<usize> {
958        self.read.blocking_read(buf)
959    }
960
961    pub fn blocking_read_chunk(&self, max: usize) -> Result<Bytes> {
962        self.read.blocking_read_chunk(max)
963    }
964
965    pub fn try_write(&self, buf: &[u8]) -> Result<usize> {
966        self.write.try_write(buf)
967    }
968
969    pub fn blocking_write(&self, buf: &[u8]) -> Result<usize> {
970        self.write.blocking_write(buf)
971    }
972
973    pub fn blocking_write_all(&self, buf: &[u8]) -> Result<()> {
974        self.write.blocking_write_all(buf)
975    }
976
977    pub fn finish(&mut self) -> Result<()> {
978        self.write.finish()
979    }
980
981    pub fn reset(&mut self) {
982        self.write.reset()
983    }
984}
985
986/// The reading end of one side. Dropping it tells the peer's writer that
987/// nothing more will be read.
988pub struct ReadHalf {
989    handle: Arc<Handle>,
990}
991
992impl ReadHalf {
993    pub fn id(&self) -> StreamId {
994        self.handle.id
995    }
996
997    /// Read what is there now. `Ok(0)` is clean end of stream;
998    /// [`Error::WouldBlock`] means nothing yet.
999    pub fn try_read(&self, buf: &mut [u8]) -> Result<usize> {
1000        self.handle.try_read(buf)
1001    }
1002
1003    /// Park until something can be read, then read it. `Ok(0)` is clean
1004    /// end of stream. Blocks the thread; an async runtime uses `AsyncRead`.
1005    pub fn blocking_read(&self, buf: &mut [u8]) -> Result<usize> {
1006        loop {
1007            match self.handle.try_read(buf) {
1008                Err(Error::WouldBlock) => self.wait_readable()?,
1009                other => return other,
1010            }
1011        }
1012    }
1013
1014    /// Up to `max` bytes as one owned chunk; empty at clean end of stream.
1015    pub fn blocking_read_chunk(&self, max: usize) -> Result<Bytes> {
1016        let mut chunk = BytesMut::zeroed(max);
1017        let len = self.blocking_read(&mut chunk)?;
1018        chunk.truncate(len);
1019        Ok(chunk.freeze())
1020    }
1021
1022    /// The same wait, bounded: `false` is the timeout and nothing else.
1023    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1024    pub fn wait_readable_timeout(&self, timeout: Duration) -> Result<bool> {
1025        self.handle
1026            .wait_until_timeout(self.handle.side.read_direction(), readable, timeout)
1027    }
1028
1029    /// Park until a read would make progress or the direction ended.
1030    pub fn wait_readable(&self) -> Result<()> {
1031        self.handle
1032            .wait_until(self.handle.side.read_direction(), readable)
1033    }
1034
1035    /// Readiness for a task: `Ready` when a read would make progress or the
1036    /// direction ended, otherwise the waker is registered and `Pending`
1037    /// comes back. Runtime-neutral; the `tokio` feature builds `AsyncRead`
1038    /// on it.
1039    pub fn poll_readable(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<()>> {
1040        self.handle.poll_ready(
1041            Interest {
1042                direction: self.handle.side.read_direction(),
1043                writer: false,
1044            },
1045            readable,
1046            cx,
1047        )
1048    }
1049}
1050
1051impl Drop for ReadHalf {
1052    fn drop(&mut self) {
1053        self.handle
1054            .set_flag(self.handle.side.read_direction(), FLAG_READER_GONE);
1055    }
1056}
1057
1058/// The writing end of one side. Dropping it without [`WriteHalf::finish`]
1059/// resets the direction, as a dropped socket would.
1060pub struct WriteHalf {
1061    handle: Arc<Handle>,
1062    shut: bool,
1063}
1064
1065impl WriteHalf {
1066    pub fn id(&self) -> StreamId {
1067        self.handle.id
1068    }
1069
1070    /// Write what fits now; [`Error::WouldBlock`] when the ring is full.
1071    pub fn try_write(&self, buf: &[u8]) -> Result<usize> {
1072        self.handle.try_write(buf)
1073    }
1074
1075    /// Park until something fits, then write it. Blocks the thread.
1076    pub fn blocking_write(&self, buf: &[u8]) -> Result<usize> {
1077        loop {
1078            match self.handle.try_write(buf) {
1079                Err(Error::WouldBlock) => self.wait_writable()?,
1080                other => return other,
1081            }
1082        }
1083    }
1084
1085    /// Write everything, waiting as needed. A failure part-way has already
1086    /// committed a prefix; nothing is replayed.
1087    pub fn blocking_write_all(&self, mut buf: &[u8]) -> Result<()> {
1088        while !buf.is_empty() {
1089            let written = self.blocking_write(buf)?;
1090            buf = &buf[written..];
1091        }
1092        Ok(())
1093    }
1094
1095    /// No more bytes from this side (FIN); what is buffered still reaches
1096    /// the reader, then it sees a clean end. The other direction is
1097    /// unaffected. `AsyncWriteExt::shutdown` does the same.
1098    pub fn finish(&mut self) -> Result<()> {
1099        self.shut = true;
1100        self.handle
1101            .set_flag(self.handle.side.write_direction(), FLAG_FIN);
1102        Ok(())
1103    }
1104
1105    /// Abandon the direction: the reader gets an error and buffered bytes
1106    /// are discarded.
1107    pub fn reset(&mut self) {
1108        self.shut = true;
1109        self.handle
1110            .set_flag(self.handle.side.write_direction(), FLAG_RESET);
1111    }
1112
1113    /// The same wait, bounded: `false` is the timeout and nothing else,
1114    /// which is how a caller with a deadline of its own gives up without
1115    /// giving up its stream.
1116    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1117    pub fn wait_writable_timeout(&self, timeout: Duration) -> Result<bool> {
1118        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1119        self.handle.wait_until_timeout(
1120            self.handle.side.write_direction(),
1121            |direction| writable(direction, buffer_bytes),
1122            timeout,
1123        )
1124    }
1125
1126    /// Park until a write would make progress or the direction ended.
1127    pub fn wait_writable(&self) -> Result<()> {
1128        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1129        self.handle.wait_until(self.handle.side.write_direction(), |direction| {
1130            writable(direction, buffer_bytes)
1131        })
1132    }
1133
1134    /// Readiness for a task: `Ready` when a write would make progress or
1135    /// the direction ended, otherwise the waker is registered and `Pending`
1136    /// comes back. Runtime-neutral; the `tokio` feature builds `AsyncWrite`
1137    /// on it.
1138    pub fn poll_writable(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<()>> {
1139        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1140        self.handle.poll_ready(
1141            Interest {
1142                direction: self.handle.side.write_direction(),
1143                writer: true,
1144            },
1145            |direction| writable(direction, buffer_bytes),
1146            cx,
1147        )
1148    }
1149}
1150
1151impl Drop for WriteHalf {
1152    fn drop(&mut self) {
1153        if !self.shut {
1154            self.handle
1155                .set_flag(self.handle.side.write_direction(), FLAG_RESET);
1156        }
1157    }
1158}
1159
1160/// Whether this build can park on a shared word. A table refuses to open
1161/// where it cannot: a sleep loop wearing the shape of a wait is worse
1162/// than a clear no, and nothing above here should have to ask again.
1163#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1164pub(crate) fn waits_supported() -> bool {
1165    orbit_core::sync::supported()
1166}
1167
1168#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos")))]
1169pub(crate) fn waits_supported() -> bool {
1170    false
1171}
1172
1173/// Park until `word` no longer holds `expected`, through the platform's
1174/// shared address wait. There is no polling fallback: a table refuses to
1175/// open where the platform cannot wait, so by here it can.
1176#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1177/// Park until the word moves or `timeout` passes. `false` is the
1178/// timeout and nothing else.
1179#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1180pub(crate) fn wait_on_timeout(word: &AtomicU32, expected: u32, timeout: Duration) -> Result<bool> {
1181    orbit_core::sync::wait_word_timeout(word, expected, timeout).map_err(Error::Io)
1182}
1183
1184pub(crate) fn wait_on(word: &AtomicU32, expected: u32) -> Result<()> {
1185    orbit_core::sync::wait_word(word, expected).map_err(Error::Io)
1186}
1187
1188#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos")))]
1189pub(crate) fn wait_on(_word: &AtomicU32, _expected: u32) -> Result<()> {
1190    Err(Error::Io(std::io::Error::new(std::io::ErrorKind::Unsupported, "orbit-stream needs a platform that can wait on a shared word")))
1191}
1192
1193/// Wake everyone parked on `word`; nothing to do where nobody can park.
1194pub(crate) fn wake_on(word: &AtomicU32) {
1195    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1196    let _ = orbit_core::sync::wake_word(word);
1197    #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos")))]
1198    let _ = word;
1199}
1200
1201pub(crate) fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
1202    mutex.lock().unwrap_or_else(|error| error.into_inner())
1203}
1204
1205#[cfg(test)]
1206mod tests {
1207    use std::sync::Arc;
1208    use std::time::Duration;
1209
1210    use orbit_core::{Fleet, NodeId};
1211
1212    use super::{
1213        Error, Incarnation, STREAM_BUFFER_BYTES, STREAM_LANE_CAPACITY, Side, Streams, Ticket,
1214    };
1215
1216    fn streams(name: &'static str) -> Streams {
1217        Streams::new(Arc::new(Fleet::join(name, 2).unwrap()), Incarnation::new(1)).unwrap()
1218    }
1219
1220    #[test]
1221    fn bytes_cross_in_order_and_end_cleanly() {
1222        let streams = streams("stream-order");
1223        let (mut a, ticket) = streams.create().unwrap();
1224        assert_eq!(ticket.side, Side::B);
1225        assert_eq!(ticket.id.node(), 0);
1226        assert_eq!(ticket.epoch, streams.epoch());
1227        let b = streams.open(ticket).unwrap();
1228
1229        a.blocking_write_all(b"hello, ").unwrap();
1230        a.blocking_write_all(b"world").unwrap();
1231        a.finish().unwrap();
1232
1233        let mut out = Vec::new();
1234        let mut buf = [0_u8; 4];
1235        loop {
1236            let n = b.blocking_read(&mut buf).unwrap();
1237            if n == 0 {
1238                break;
1239            }
1240            out.extend_from_slice(&buf[..n]);
1241        }
1242        assert_eq!(out, b"hello, world");
1243        // The other direction is its own thing.
1244        b.blocking_write_all(b"back").unwrap();
1245        assert_eq!(a.blocking_read_chunk(16).unwrap().as_ref(), b"back");
1246    }
1247
1248    #[test]
1249    fn a_ticket_prints_and_parses() {
1250        let streams = streams("stream-ticket");
1251        let (_a, ticket) = streams.create().unwrap();
1252        let text = ticket.to_string();
1253        assert!(text.contains("/b/"), "{text}");
1254        assert_eq!(text.parse::<Ticket>().unwrap(), ticket);
1255        assert!(matches!("nope".parse::<Ticket>(), Err(Error::Malformed(_))));
1256        assert!(matches!("x/b".parse::<Ticket>(), Err(Error::Malformed(_))));
1257    }
1258
1259    #[test]
1260    fn a_side_is_held_once_and_a_released_stream_goes_stale() {
1261        let streams = streams("stream-claim");
1262        let (a, ticket) = streams.create().unwrap();
1263        let b = streams.open(ticket).unwrap();
1264        assert!(matches!(
1265            streams.open(ticket),
1266            Err(Error::AlreadyClaimed(_))
1267        ));
1268        assert!(streams.is_live(ticket.id));
1269
1270        drop(a);
1271        // A's write half went without finishing: B reads a reset.
1272        let mut buf = [0_u8; 1];
1273        assert!(matches!(b.blocking_read(&mut buf), Err(Error::Reset)));
1274        drop(b);
1275        assert!(!streams.is_live(ticket.id));
1276        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
1277
1278        // Once the lane wraps, the slot is reused under a new generation and
1279        // the old ticket stays stale.
1280        for _ in 0..STREAM_LANE_CAPACITY - 1 {
1281            drop(streams.create().unwrap());
1282        }
1283        let (_a2, ticket2) = streams.create().unwrap();
1284        assert_eq!(ticket2.id.slot(), ticket.id.slot());
1285        assert_ne!(ticket2.id.generation(), ticket.id.generation());
1286        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
1287    }
1288
1289    #[test]
1290    fn a_reset_starts_a_new_epoch_and_refuses_old_tickets() {
1291        let streams = streams("stream-epoch");
1292        let (_a, ticket) = streams.create().unwrap();
1293        let before = streams.epoch();
1294        streams.reset_all();
1295        assert!(streams.epoch() > before);
1296        // The slot is free again and reinstalled from generation one, which
1297        // is exactly what the old ticket names; the epoch tells them apart.
1298        let (_a2, ticket2) = streams.create().unwrap();
1299        assert_eq!(ticket2.id, ticket.id);
1300        assert_ne!(ticket2.epoch, ticket.epoch);
1301        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
1302        assert!(streams.open(ticket2).is_ok());
1303    }
1304
1305    #[test]
1306    fn a_slot_whose_generation_runs_out_is_not_reused() {
1307        // GENERATION_LIMIT is 4 under test: each slot can be installed four
1308        // times in one epoch, then it sits out until the next epoch.
1309        let streams = streams("stream-exhaust");
1310        let limit = super::layout::GENERATION_LIMIT as usize;
1311        for _ in 0..STREAM_LANE_CAPACITY * limit {
1312            drop(streams.create().unwrap());
1313        }
1314        assert!(matches!(streams.create(), Err(Error::Full { .. })));
1315        streams.reset_all();
1316        let (_a, ticket) = streams.create().unwrap();
1317        assert_eq!(ticket.id.generation(), 1);
1318    }
1319
1320    #[test]
1321    fn a_full_ring_blocks_the_writer_and_wraps_correctly() {
1322        let streams = streams("stream-wrap");
1323        let (a, ticket) = streams.create().unwrap();
1324        let b = streams.open(ticket).unwrap();
1325
1326        let filler = vec![7_u8; STREAM_BUFFER_BYTES];
1327        assert_eq!(a.try_write(&filler).unwrap(), STREAM_BUFFER_BYTES);
1328        assert!(matches!(a.try_write(b"x"), Err(Error::WouldBlock)));
1329
1330        let mut buf = vec![0_u8; STREAM_BUFFER_BYTES - 5];
1331        assert_eq!(b.blocking_read(&mut buf).unwrap(), STREAM_BUFFER_BYTES - 5);
1332        // Room again; the next write wraps around the end of the ring.
1333        let tail = (0..STREAM_BUFFER_BYTES)
1334            .map(|i| i as u8)
1335            .collect::<Vec<_>>();
1336        assert_eq!(a.try_write(&tail).unwrap(), STREAM_BUFFER_BYTES - 5);
1337
1338        let mut rest = vec![0_u8; 5];
1339        assert_eq!(b.blocking_read(&mut rest).unwrap(), 5);
1340        assert_eq!(rest, vec![7_u8; 5]);
1341        let mut wrapped = vec![0_u8; STREAM_BUFFER_BYTES];
1342        let mut got = 0;
1343        while got < STREAM_BUFFER_BYTES - 5 {
1344            got += b.blocking_read(&mut wrapped[got..]).unwrap();
1345        }
1346        assert_eq!(&wrapped[..got], &tail[..got]);
1347    }
1348
1349    #[test]
1350    fn a_blocked_reader_is_woken_by_a_write_in_another_thread() {
1351        let streams = streams("stream-wake");
1352        let (a, ticket) = streams.create().unwrap();
1353        let b = streams.open(ticket).unwrap();
1354        let reader = std::thread::spawn(move || {
1355            let mut buf = [0_u8; 8];
1356            let n = b.blocking_read(&mut buf).unwrap();
1357            buf[..n].to_vec()
1358        });
1359        std::thread::sleep(Duration::from_millis(30));
1360        a.blocking_write_all(b"ping").unwrap();
1361        assert_eq!(reader.join().unwrap(), b"ping");
1362    }
1363
1364    #[test]
1365    fn a_reader_gone_fails_the_writer_and_a_lane_fills_up() {
1366        let streams = streams("stream-gone");
1367        let (a, ticket) = streams.create().unwrap();
1368        let b = streams.open(ticket).unwrap();
1369        let (b_read, _b_write) = b.split();
1370        drop(b_read);
1371        assert!(matches!(a.blocking_write(b"nobody"), Err(Error::PeerGone)));
1372
1373        let mut held = vec![a];
1374        for _ in 1..STREAM_LANE_CAPACITY {
1375            held.push(streams.create().unwrap().0);
1376        }
1377        assert!(matches!(streams.create(), Err(Error::Full { .. })));
1378    }
1379
1380    #[test]
1381    fn a_death_report_ends_that_incarnations_sides_only() {
1382        let streams = streams("stream-dead");
1383        let (a, ticket) = streams.create().unwrap();
1384        let b = streams.open(ticket).unwrap();
1385        b.blocking_write_all(b"partial").unwrap();
1386
1387        // Another incarnation of the same node: nothing happens.
1388        streams.node_dead(NodeId::ZERO, Incarnation::new(2));
1389        assert_eq!(a.blocking_read_chunk(16).unwrap().as_ref(), b"partial");
1390
1391        // Our incarnation died (as a supervisor would report about a peer):
1392        // both sides here are stamped with it, so both end and the slot
1393        // empties at once. A parked reader wakes with an error either way;
1394        // the two-incarnation case, where the survivor keeps the slot and
1395        // reads a reset, is the shared-memory test.
1396        let reader = std::thread::spawn(move || {
1397            let mut buf = [0_u8; 8];
1398            a.blocking_read(&mut buf).map(|_| ())
1399        });
1400        std::thread::sleep(Duration::from_millis(30));
1401        streams.node_dead(NodeId::ZERO, Incarnation::new(1));
1402        assert!(matches!(
1403            reader.join().unwrap(),
1404            Err(Error::Reset | Error::Stale(_))
1405        ));
1406        assert!(matches!(
1407            b.try_write(b"x"),
1408            Err(Error::Reset | Error::Stale(_))
1409        ));
1410        assert!(matches!(
1411            streams.open(ticket),
1412            Err(Error::AlreadyClaimed(_) | Error::Stale(_))
1413        ));
1414        drop(b);
1415        assert!(!streams.is_live(ticket.id));
1416    }
1417
1418    #[test]
1419    fn an_offer_is_taken_once_and_a_stale_offer_is_skipped() {
1420        let streams = streams("stream-offer");
1421        assert!(streams.take_offer().is_none());
1422        let (a, ticket) = streams.create().unwrap();
1423        streams.offer(ticket, NodeId::ZERO).unwrap();
1424        assert_eq!(streams.take_offer(), Some(ticket));
1425        assert!(streams.take_offer().is_none());
1426
1427        let (gone, stale) = streams.create().unwrap();
1428        streams.offer(stale, NodeId::ZERO).unwrap();
1429        drop(gone);
1430        assert!(streams.take_offer().is_none());
1431
1432        let taker = streams.clone();
1433        let waiter = std::thread::spawn(move || taker.blocking_take_offer());
1434        std::thread::sleep(Duration::from_millis(30));
1435        streams.offer(ticket, NodeId::ZERO).unwrap();
1436        assert_eq!(waiter.join().unwrap().unwrap(), ticket);
1437        drop(a);
1438    }
1439
1440    #[test]
1441    fn a_contested_claim_goes_to_exactly_one_opener() {
1442        let streams = streams("stream-race");
1443        for _ in 0..50 {
1444            let (_a, ticket) = streams.create().unwrap();
1445            let openers = (0..4)
1446                .map(|_| {
1447                    let streams = streams.clone();
1448                    std::thread::spawn(move || streams.open(ticket).is_ok())
1449                })
1450                .collect::<Vec<_>>();
1451            let won = openers
1452                .into_iter()
1453                .map(|opener| opener.join().unwrap())
1454                .filter(|won| *won)
1455                .count();
1456            assert_eq!(won, 1);
1457        }
1458    }
1459
1460    #[test]
1461    fn random_sizes_concatenate_exactly() {
1462        let streams = streams("stream-random");
1463        let (mut a, ticket) = streams.create().unwrap();
1464        let b = streams.open(ticket).unwrap();
1465        let payload = (0..300_000_u32)
1466            .map(|i| (i.wrapping_mul(2654435761) >> 13) as u8)
1467            .collect::<Vec<_>>();
1468        let expected = payload.clone();
1469        let writer = std::thread::spawn(move || {
1470            let mut rest = &payload[..];
1471            let mut step = 1_usize;
1472            while !rest.is_empty() {
1473                let len = step.min(rest.len());
1474                a.blocking_write_all(&rest[..len]).unwrap();
1475                rest = &rest[len..];
1476                step = (step * 7 + 13) % 9_001 + 1;
1477            }
1478            a.finish().unwrap();
1479        });
1480        let mut out = Vec::new();
1481        let mut step = 3_usize;
1482        loop {
1483            let chunk = b.blocking_read_chunk(step).unwrap();
1484            if chunk.is_empty() {
1485                break;
1486            }
1487            out.extend_from_slice(&chunk);
1488            step = (step * 5 + 11) % 4_099 + 1;
1489        }
1490        writer.join().unwrap();
1491        assert_eq!(out, expected);
1492    }
1493}