Skip to main content

orbit_stream/
lib.rs

1//! Bounded byte streams and paired exchanges between members of an Orbit
2//! fleet.
3//!
4//! A stream is a place with two ends. [`Streams::create`] takes a slot in
5//! this process's lane, hands back the endpoint for side A and a [`Ticket`]
6//! for side B; whoever in the fleet [`Streams::open`]s that ticket holds the
7//! other end. Each direction is its own bounded ring: bytes go in at one end
8//! and come out, in order and without loss, at the other; when the ring is
9//! full the writer waits, when it is empty the reader waits. A writer that
10//! is done shuts its direction down (FIN) and the reader drains to a clean
11//! end; a writer that gives up resets it and the reader sees an error.
12//!
13//! Standalone the rings live in process memory; in a shared-memory fleet
14//! they live in the fleet's segment and the two ends may be in different
15//! processes. The address that travels is a [`NetId64`]: its kind is the
16//! segment's, its node the creator's lane, its counter the slot and the
17//! slot's generation; the [`Ticket`] around it adds the table's epoch. An
18//! address kept past the stream's end, or past a reset, answers
19//! [`Error::Stale`] instead of reaching whoever took the slot next.
20//!
21//! The blocking calls park on the direction's own change word through the
22//! platform's shared address wait, like a cell. With the `tokio` feature the
23//! halves implement `AsyncRead` and `AsyncWrite`; a poll that finds nothing
24//! registers its waker and returns `Pending`, and one thread per process
25//! turns the fleet's doorbell into those wakes. No thread and no descriptor
26//! per stream.
27//!
28//! [`exchange`] builds a symmetric event program above that lossless control
29//! path. Payload bytes live in one shared arena whose per-node lanes make
30//! allocation ownership explicit: fixed-size slots are physical allocation
31//! units, while each published chunk is one descriptor spanning as many
32//! contiguous slots as that runtime decision needs. Both directions have
33//! independent START, FIN and RESET state, so either may begin first without
34//! imposing an application sequencing rule on the transport.
35
36use std::str::FromStr;
37use std::sync::atomic::{AtomicU32, Ordering};
38use std::sync::{Arc, Mutex, MutexGuard};
39use std::time::{Duration, Instant};
40use std::{fmt, io};
41
42use bytes::{Bytes, BytesMut};
43use orbit_core::{Fleet, NetId64};
44
45#[cfg(unix)]
46mod bell;
47pub mod exchange;
48mod layout;
49#[cfg(feature = "tokio")]
50mod poll;
51mod table;
52mod wake;
53
54use layout::{
55    Direction, FLAG_FIN, FLAG_READER_GONE, FLAG_RESET, GENERATION_MASK,
56    SIDE_CLAIMED, SIDE_DEAD, SIDE_FREE, SIDE_RELEASED, SLOT_BITS, SLOT_EMPTY,
57    SLOT_LIVE, SLOT_MASK, Slot,
58};
59use orbit_core::NodeId;
60#[cfg(any(
61    target_os = "linux",
62    target_os = "freebsd",
63    target_os = "macos"
64))]
65pub use orbit_core::readiness::Readiness;
66use table::Table;
67pub use table::{segment_size, segment_size_for};
68use wake::Interest;
69
70/// Reserved Orbit SHM kind for the default stream segment. Another table
71/// names its own through a [`StreamSpec`].
72pub const STREAM_KIND: u8 = 246;
73/// Streams one fleet node can hold open at once, in the default spec.
74///
75/// Compile-time geometry: `ORBIT_STREAM_LANE_CAPACITY` in the application's
76/// `.cargo/config.toml` overrides the default. A power of two, at most
77/// 65 536. It is the *default* table's value; a [`StreamSpec`] gives
78/// another table another one. Peers opening the same kind with a different
79/// value are refused at the segment.
80pub const STREAM_LANE_CAPACITY: usize = orbit_core::compile::usize_from_env(
81    option_env!("ORBIT_STREAM_LANE_CAPACITY"),
82    256,
83);
84/// Bytes each direction of a stream can hold before its writer waits, in
85/// the default spec.
86///
87/// Compile-time geometry: `ORBIT_STREAM_BUFFER_BYTES`. A power of two. Part
88/// of the same wire contract as the lane capacity, and like it a per-table
89/// choice through [`StreamSpec`].
90pub const STREAM_BUFFER_BYTES: usize = orbit_core::compile::usize_from_env(
91    option_env!("ORBIT_STREAM_BUFFER_BYTES"),
92    64 * 1024,
93);
94
95/// Which segment a [`Streams`] table uses, and how big its lanes and
96/// rings are.
97///
98/// One fleet can hold several independent stream tables. They are not
99/// interchangeable: a relay carrying response bodies wants a ring sized to
100/// a body, while a control channel carrying short frames is faster with a
101/// small one, and a lane is a ceiling on concurrent streams per node. Each
102/// names its own kind, and a kind is a fleet-wide identity — every process
103/// opening it must pass the same geometry, and the segment's header
104/// refuses a peer that does not. [`StreamSpec::DEFAULT`] is what
105/// [`Streams::new`] opens; its values are the compile-time geometry.
106#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
107pub struct StreamSpec {
108    /// The Orbit SHM kind, and with it the segment name.
109    pub kind: u8,
110    /// Streams one fleet node can hold open at once. A power of two, at
111    /// most 65 536.
112    pub lane_capacity: usize,
113    /// Bytes each direction holds before its writer waits. A power of two.
114    pub buffer_bytes: usize,
115}
116
117impl StreamSpec {
118    pub const DEFAULT: Self =
119        Self::new(STREAM_KIND, STREAM_LANE_CAPACITY, STREAM_BUFFER_BYTES);
120
121    pub const fn new(
122        kind: u8,
123        lane_capacity: usize,
124        buffer_bytes: usize,
125    ) -> Self {
126        Self { kind, lane_capacity, buffer_bytes }
127    }
128
129    /// The segment one node's streams occupy at most: every slot's two
130    /// rings. Memory is backed as it is touched, but this is the ceiling
131    /// a deployment is choosing when it picks a lane and a ring.
132    pub const fn lane_bytes(&self) -> usize {
133        self.lane_capacity * 2 * self.buffer_bytes
134    }
135
136    /// What the compile-time geometry used to assert. A spec is checked
137    /// once, when its table is opened.
138    fn validate(self) -> Result<()> {
139        if self.lane_capacity == 0
140            || self.buffer_bytes == 0
141            || !self.lane_capacity.is_power_of_two()
142            || !self.buffer_bytes.is_power_of_two()
143            || self.lane_capacity > 1 << SLOT_BITS
144            || self.buffer_bytes > u32::MAX as usize
145        {
146            return Err(Error::Malformed(format!(
147                "stream spec kind={} lane_capacity={} buffer_bytes={}: both are powers of two, a lane holds at most {} streams and a ring at most {} bytes",
148                self.kind,
149                self.lane_capacity,
150                self.buffer_bytes,
151                1_usize << SLOT_BITS,
152                u32::MAX
153            )));
154        }
155        Ok(())
156    }
157}
158
159impl Default for StreamSpec {
160    fn default() -> Self {
161        Self::DEFAULT
162    }
163}
164
165pub type Result<T, E = Error> = std::result::Result<T, E>;
166
167/// Additions are expected: a cause discovered later lands here rather
168/// than in a new major version, so a caller matches what it handles and
169/// leaves the rest to a catch-all.
170#[non_exhaustive]
171#[derive(Debug)]
172pub enum Error {
173    /// The address names a slot nothing occupies, or a generation that ended.
174    Stale(StreamId),
175    /// That side of the stream is already held, or was held and released.
176    AlreadyClaimed(Ticket),
177    /// Every slot in this process's lane is in use.
178    Full {
179        capacity: usize,
180    },
181    /// The producer's payload arena has no contiguous run large enough.
182    PayloadFull {
183        requested_slots: usize,
184    },
185    /// One chunk cannot fit in a lane of the selected payload arena.
186    PayloadTooLarge {
187        len: usize,
188        capacity: usize,
189    },
190    /// The text or id is not a stream address.
191    Malformed(String),
192    /// This write half was shut down; nothing more goes in.
193    Closed,
194    /// The other end reset the direction; buffered bytes were discarded.
195    Reset,
196    /// The other end's reader is gone; nothing written here will be read.
197    PeerGone,
198    /// Nothing to read, or no room to write, right now.
199    WouldBlock,
200    Io(io::Error),
201}
202
203impl fmt::Display for Error {
204    fn fmt(
205        &self,
206        f: &mut fmt::Formatter<'_>,
207    ) -> fmt::Result {
208        match self {
209            Self::Stale(id) => write!(f, "stream {id} has ended"),
210            Self::AlreadyClaimed(ticket) => {
211                write!(f, "stream side {ticket} is already held")
212            }
213            Self::Full { capacity } => {
214                write!(f, "stream lane is full: capacity={capacity}")
215            }
216            Self::PayloadFull { requested_slots } => {
217                write!(
218                    f,
219                    "payload arena has no run of {requested_slots} free slots"
220                )
221            }
222            Self::PayloadTooLarge { len, capacity } => {
223                write!(
224                    f,
225                    "payload chunk is {len} bytes; arena lane capacity is {capacity}"
226                )
227            }
228            Self::Malformed(text) => {
229                write!(f, "not a stream address: {text:?}")
230            }
231            Self::Closed => f.write_str("stream write half is shut down"),
232            Self::Reset => {
233                f.write_str("stream direction was reset by the peer")
234            }
235            Self::PeerGone => f.write_str("stream peer reader is gone"),
236            Self::WouldBlock => f.write_str("stream operation would block"),
237            Self::Io(error) => write!(f, "Orbit stream io error: {error}"),
238        }
239    }
240}
241
242impl std::error::Error for Error {
243    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
244        match self {
245            Self::Io(error) => Some(error),
246            _ => None,
247        }
248    }
249}
250
251impl From<io::Error> for Error {
252    fn from(value: io::Error) -> Self {
253        Self::Io(value)
254    }
255}
256
257impl From<Error> for io::Error {
258    fn from(value: Error) -> Self {
259        let kind = match &value {
260            Error::Stale(_) | Error::AlreadyClaimed(_) => {
261                io::ErrorKind::NotConnected
262            }
263            Error::Full { .. }
264            | Error::PayloadFull { .. }
265            | Error::PayloadTooLarge { .. } => io::ErrorKind::OutOfMemory,
266            Error::Malformed(_) => io::ErrorKind::InvalidInput,
267            Error::Closed => io::ErrorKind::BrokenPipe,
268            Error::Reset => io::ErrorKind::ConnectionReset,
269            Error::PeerGone => io::ErrorKind::BrokenPipe,
270            Error::WouldBlock => io::ErrorKind::WouldBlock,
271            Error::Io(error) => error.kind(),
272        };
273        io::Error::new(kind, value)
274    }
275}
276
277/// The address of one stream: a [`NetId64`] whose node is the creator's
278/// lane and whose counter is the slot and its generation.
279#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
280pub struct StreamId(NetId64);
281
282impl StreamId {
283    pub const fn from_net_id(id: NetId64) -> Self {
284        Self(id)
285    }
286
287    pub const fn net_id(self) -> NetId64 {
288        self.0
289    }
290
291    pub const fn kind(self) -> u8 {
292        self.0.kind()
293    }
294
295    /// The lane: the fleet node that created the stream.
296    pub const fn node(self) -> u16 {
297        self.0.node()
298    }
299
300    pub const fn slot(self) -> u32 {
301        (self.0.counter() & SLOT_MASK) as u32
302    }
303
304    pub const fn generation(self) -> u32 {
305        (self.0.counter() >> SLOT_BITS) as u32
306    }
307
308    fn make(
309        kind: u8,
310        node: u16,
311        slot: u32,
312        generation: u32,
313    ) -> Self {
314        Self(NetId64::make(
315            kind,
316            node,
317            ((generation as u64) << SLOT_BITS) | (slot as u64 & SLOT_MASK),
318        ))
319    }
320}
321
322impl fmt::Display for StreamId {
323    fn fmt(
324        &self,
325        f: &mut fmt::Formatter<'_>,
326    ) -> fmt::Result {
327        self.0.fmt(f)
328    }
329}
330
331impl FromStr for StreamId {
332    type Err = Error;
333
334    fn from_str(text: &str) -> Result<Self> {
335        text.parse::<NetId64>()
336            .map(Self)
337            .map_err(|_| Error::Malformed(text.to_owned()))
338    }
339}
340
341/// Which end of a stream a handle holds. The creator holds `A`.
342#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
343pub enum Side {
344    A,
345    B,
346}
347
348impl Side {
349    const fn index(self) -> usize {
350        match self {
351            Self::A => 0,
352            Self::B => 1,
353        }
354    }
355
356    /// The direction this side writes into.
357    const fn write_direction(self) -> usize {
358        self.index()
359    }
360
361    /// The direction this side reads from.
362    const fn read_direction(self) -> usize {
363        1 - self.index()
364    }
365
366    const fn letter(self) -> char {
367        match self {
368            Self::A => 'a',
369            Self::B => 'b',
370        }
371    }
372}
373
374/// One end of a stream, in the form that crosses a setup channel: the
375/// address, the side, and the epoch of the table the address was minted
376/// in. Prints as `<address>/b/<epoch>`.
377///
378/// Two things keep a ticket from reaching the wrong memory: the slot
379/// generation inside the address separates reuses of a slot within one
380/// epoch, and the epoch separates before and after a quiescent reset or a
381/// recreated segment, which reinstalls slots from generation one.
382#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
383pub struct Ticket {
384    pub id: StreamId,
385    pub side: Side,
386    pub epoch: u64,
387}
388
389impl fmt::Display for Ticket {
390    fn fmt(
391        &self,
392        f: &mut fmt::Formatter<'_>,
393    ) -> fmt::Result {
394        write!(f, "{}/{}/{}", self.id, self.side.letter(), self.epoch)
395    }
396}
397
398impl FromStr for Ticket {
399    type Err = Error;
400
401    fn from_str(text: &str) -> Result<Self> {
402        let malformed = || Error::Malformed(text.to_owned());
403        let (rest, epoch) = text.rsplit_once('/').ok_or_else(malformed)?;
404        let (id, side) = rest.rsplit_once('/').ok_or_else(malformed)?;
405        let side = match side {
406            "a" => Side::A,
407            "b" => Side::B,
408            _ => return Err(malformed()),
409        };
410        Ok(Self {
411            id: id.parse().map_err(|_| malformed())?,
412            side,
413            epoch: epoch.parse().map_err(|_| malformed())?,
414        })
415    }
416}
417
418/// Which life of a process holds a side. A node id is a role that a
419/// replacement process inherits; the incarnation tells the two apart, so a
420/// late death report for the old process never touches the new one's
421/// streams. Orbit does not mint it: the embedder knows what identifies one
422/// live process (a start stamp, a supervisor generation) and supplies it.
423#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
424pub struct Incarnation(u64);
425
426impl Incarnation {
427    pub const fn new(value: u64) -> Self {
428        Self(value)
429    }
430
431    pub const fn get(self) -> u64 {
432        self.0
433    }
434}
435
436/// The fleet's stream table. Cheap to clone; every clone in a process is
437/// the same table, the same driver and the same wakers.
438#[derive(Clone)]
439pub struct Streams {
440    table: Arc<Table>,
441}
442
443impl Streams {
444    /// Open only a dedicated group-shared stream table. The supplied identity
445    /// does not change any Fleet policy or publish other shared resources.
446    #[cfg(unix)]
447    pub fn with_group(
448        name: &str,
449        capacity: u16,
450        node: NodeId,
451        incarnation: Incarnation,
452        spec: StreamSpec,
453        access: orbit_core::shm::GroupAccess,
454    ) -> Result<Self> {
455        spec.validate()?;
456        Ok(Self {
457            table: table::open_group(
458                name,
459                capacity,
460                node,
461                incarnation,
462                spec,
463                access,
464                false,
465            )?,
466        })
467    }
468
469    /// Initialize/validate transport before its inbox becomes joinable.
470    #[cfg(unix)]
471    pub fn prepare_group(
472        name: &str,
473        capacity: u16,
474        spec: StreamSpec,
475        access: orbit_core::shm::GroupAccess,
476    ) -> Result<()> {
477        spec.validate()?;
478        table::open_group(
479            name,
480            capacity,
481            NodeId::ZERO,
482            Incarnation::new(0),
483            spec,
484            access,
485            true,
486        )?;
487        Ok(())
488    }
489
490    /// Recover an exact incarnation under its caller-held Link lane lock.
491    #[cfg(unix)]
492    pub fn node_dead_in_group(
493        name: &str,
494        capacity: u16,
495        spec: StreamSpec,
496        node: NodeId,
497        incarnation: Incarnation,
498        access: orbit_core::shm::GroupAccess,
499    ) -> Result<()> {
500        spec.validate()?;
501        if node.get() >= capacity {
502            return Err(Error::Malformed(
503                "recovery node is outside the fleet".into(),
504            ));
505        }
506        let table = table::open_group(
507            name,
508            capacity,
509            NodeId::ZERO,
510            Incarnation::new(0),
511            spec,
512            access,
513            true,
514        )?;
515        table.node_dead(node.get(), incarnation.get());
516        Ok(())
517    }
518
519    /// Report one confirmed death to an existing shared table without
520    /// claiming an endpoint, superseding a node, or installing a driver.
521    /// Returns false when the table does not exist; never creates it then.
522    #[cfg(unix)]
523    pub fn node_dead_in_shm(
524        name: &str,
525        fleet_capacity: u16,
526        spec: StreamSpec,
527        node: NodeId,
528        incarnation: Incarnation,
529    ) -> Result<bool> {
530        spec.validate()?;
531        if node.get() >= fleet_capacity {
532            return Err(Error::Malformed(
533                "recovery node is outside the fleet".into(),
534            ));
535        }
536        let segment = orbit_core::shm::ring_segment_name(name, spec.kind);
537        match orbit_core::shm::ShmRegion::validate_existing(
538            &segment,
539            segment_size_for(fleet_capacity, spec),
540        )? {
541            orbit_core::shm::ShmValidation::Missing => Ok(false),
542            orbit_core::shm::ShmValidation::Valid { .. } => {
543                table::recover_shm(
544                    name,
545                    fleet_capacity,
546                    spec,
547                    node.get(),
548                    incarnation.get(),
549                )?;
550                Ok(true)
551            }
552        }
553    }
554
555    /// Open the fleet's table as this process's incarnation. Every side
556    /// this process claims is stamped with it, and [`Streams::node_dead`]
557    /// for the same node and incarnation is what ends those sides.
558    pub fn new(
559        fleet: Arc<Fleet>,
560        incarnation: Incarnation,
561    ) -> Result<Self> {
562        Self::with_spec(fleet, incarnation, StreamSpec::DEFAULT)
563    }
564
565    /// Open the table `spec` names. Independent specs are independent
566    /// tables: separate segments, separate lanes and rings, separate
567    /// epochs, and a `reset_all` on one leaves the others alone. A process
568    /// may hold as many as it has specs, but each under one incarnation.
569    pub fn with_spec(
570        fleet: Arc<Fleet>,
571        incarnation: Incarnation,
572        spec: StreamSpec,
573    ) -> Result<Self> {
574        spec.validate()?;
575        Ok(Self { table: table::open(&fleet, incarnation, spec)? })
576    }
577
578    /// The kind this table's segment lives under.
579    pub fn kind(&self) -> u8 {
580        self.table.kind()
581    }
582
583    /// A descriptor that becomes readable when something among this
584    /// process's streams may have changed, for a runtime that parks on
585    /// descriptors rather than on wakers.
586    ///
587    /// Edge-triggered and coalescing: drain it, then re-try the
588    /// non-blocking calls on the streams you hold. It is what makes this
589    /// table usable from a foreign event loop — an embedded runtime, a
590    /// reactor of its own — without a thread or a descriptor per stream.
591    ///
592    /// One per table: a second caller is refused rather than handed a
593    /// descriptor whose signals the first would drain.
594    #[cfg(any(
595        target_os = "linux",
596        target_os = "freebsd",
597        target_os = "macos"
598    ))]
599    pub fn readiness(&self) -> Result<Readiness> {
600        self.table.take_readiness()
601    }
602
603    /// The epoch every ticket minted from this table carries right now.
604    pub fn epoch(&self) -> u64 {
605        self.table.epoch()
606    }
607
608    /// Take a slot in this process's lane. The endpoint is side A; the
609    /// ticket names side B and is what the other end opens.
610    pub fn create(&self) -> Result<(Endpoint, Ticket)> {
611        let (index, generation) = self.table.allocate()?;
612        let id = StreamId::make(
613            self.table.kind(),
614            self.table.node(),
615            (index % self.table.geometry().lane_capacity) as u32,
616            generation,
617        );
618        let handle = Arc::new(Handle {
619            table: Arc::clone(&self.table),
620            index,
621            id,
622            side: Side::A,
623        });
624        Ok((
625            Endpoint::new(handle),
626            Ticket { id, side: Side::B, epoch: self.table.epoch() },
627        ))
628    }
629
630    /// Hold the side a ticket names. Each side can be held once per stream.
631    pub fn open(
632        &self,
633        ticket: Ticket,
634    ) -> Result<Endpoint> {
635        let index = self.locate(ticket.id)?;
636        if ticket.epoch != self.table.epoch() {
637            return Err(Error::Stale(ticket.id));
638        }
639        let slot = &self.table.slots()[index];
640        if !slot.is(ticket.id.generation()) {
641            return Err(Error::Stale(ticket.id));
642        }
643        let claimed = &slot.claimed[ticket.side.index()];
644        if claimed
645            .compare_exchange(
646                SIDE_FREE,
647                SIDE_CLAIMED,
648                Ordering::SeqCst,
649                Ordering::SeqCst,
650            )
651            .is_err()
652        {
653            return Err(Error::AlreadyClaimed(ticket));
654        }
655        // The slot may have ended between the check and the claim; give the
656        // claim back rather than hold a side of whatever comes next.
657        if !slot.is(ticket.id.generation()) {
658            let _ = claimed.compare_exchange(
659                SIDE_CLAIMED,
660                SIDE_FREE,
661                Ordering::SeqCst,
662                Ordering::SeqCst,
663            );
664            return Err(Error::Stale(ticket.id));
665        }
666        slot.incarnation[ticket.side.index()]
667            .store(self.table.incarnation(), Ordering::Release);
668        slot.node[ticket.side.index()]
669            .store(self.table.node(), Ordering::Release);
670        // Counted for blocking waiters, and no doorbell rung. A claim is not
671        // something a parked task can be waiting for: readiness is `head`,
672        // `tail` and the flags, and no predicate reads who holds a side. A
673        // ring here woke both processes' drivers on every open to find
674        // nothing ready — measured, it was most of what an exchange cost
675        // above a Unix socket.
676        for direction in &slot.directions {
677            self.table.notify(index, slot, direction, table::Ring::Nobody);
678        }
679        Ok(Endpoint::new(Arc::new(Handle {
680            table: Arc::clone(&self.table),
681            index,
682            id: ticket.id,
683            side: ticket.side,
684        })))
685    }
686
687    /// Clear a stream for another exchange, and let its far side be opened
688    /// again — by any process.
689    ///
690    /// A slot created and released per exchange pays for both every time: two
691    /// compare-and-swaps, a reset, and a ring nobody has touched since it was
692    /// last used, so cold in cache and absent from the TLB. A creator that
693    /// keeps its slots between exchanges calls this instead. Its own claim
694    /// stands and the generation does not move, so the ticket stays valid;
695    /// the directions go back to nothing, and the far side changes hands.
696    ///
697    /// **The far side has to have let go, and the table is what says so.** Its
698    /// halves raise their flags as they drop and its claim is released after
699    /// them, so a claim that reads released belongs to a process that can no
700    /// longer touch the slot. That is the whole handshake — no message, no
701    /// flag to race with — and it is why the slot belongs to its creator
702    /// rather than to a pair: once reopened, whichever process the next
703    /// exchange is addressed to opens it.
704    ///
705    /// - `Ok`: re-armed; the next ticket written for it can be opened.
706    /// - [`Error::WouldBlock`]: the far side still holds it. Not yet — try
707    ///   another slot.
708    /// - [`Error::Stale`]: let it go. The address no longer names this stream,
709    ///   the far side died, or it never opened this exchange at all — and a
710    ///   side nobody opened may still be opened late by whoever the last
711    ///   ticket went to, so it is not safe to hand to anyone else. Dropping the
712    ///   endpoint empties the slot, and a late open is then refused as stale.
713    ///
714    /// The far side's owner goes back to what [`Streams::create`] writes for
715    /// nobody before its claim opens. A death report reads the claim first and
716    /// the owner after it, so without this a report about the process that
717    /// just let go could land on the one that opens next.
718    ///
719    /// **The caller owns the ordering.** Whoever re-arms must do it *before*
720    /// telling the other side that a new exchange has begun, so that the
721    /// message announcing the exchange is what publishes the cleared state.
722    pub fn rearm(
723        &self,
724        id: StreamId,
725    ) -> Result<()> {
726        let index = self.locate(id)?;
727        let slot = &self.table.slots()[index];
728        if !slot.is(id.generation()) {
729            return Err(Error::Stale(id));
730        }
731        let far = Side::B.index();
732        match slot.claimed[far].load(Ordering::SeqCst) {
733            SIDE_RELEASED => {}
734            SIDE_CLAIMED => return Err(Error::WouldBlock),
735            _ => return Err(Error::Stale(id)),
736        }
737        for direction in &slot.directions {
738            direction.clear();
739        }
740        slot.node[far].store(u16::MAX, Ordering::Relaxed);
741        slot.incarnation[far].store(0, Ordering::Relaxed);
742        // Released is terminal for its holder and a death report only touches
743        // a claimed side, so nothing else moves this word; the exchange is
744        // what publishes everything written above it.
745        slot.claimed[far]
746            .compare_exchange(
747                SIDE_RELEASED,
748                SIDE_FREE,
749                Ordering::SeqCst,
750                Ordering::SeqCst,
751            )
752            .map_err(|_| Error::Stale(id))?;
753        Ok(())
754    }
755
756    /// Whether the address names a live stream right now.
757    pub fn is_live(
758        &self,
759        id: StreamId,
760    ) -> bool {
761        self.locate(id)
762            .map(|index| self.table.slots()[index].is(id.generation()))
763            .unwrap_or(false)
764    }
765
766    /// Tell `node`'s process that side B of this stream is its to take:
767    /// the slot is put in that node's offer bitmap and its doorbell rung.
768    /// Discovery only; the ticket may just as well travel by any other
769    /// channel. An offer is seen once, by that node, through
770    /// [`Streams::take_offer`] and its waiting forms.
771    pub fn offer(
772        &self,
773        ticket: Ticket,
774        to: NodeId,
775    ) -> Result<()> {
776        let index = self.locate(ticket.id)?;
777        if ticket.epoch != self.table.epoch()
778            || !self.table.slots()[index].is(ticket.id.generation())
779        {
780            return Err(Error::Stale(ticket.id));
781        }
782        self.table.offer(index, to.get())
783    }
784
785    /// The next stream offered to this node, if any. A stream that ended
786    /// between the offer and now is skipped.
787    pub fn take_offer(&self) -> Option<Ticket> {
788        while let Some(index) = self.table.take_offer() {
789            if let Some(ticket) = self.ticket_for(index) {
790                return Some(ticket);
791            }
792        }
793        None
794    }
795
796    /// The same wait, bounded: `None` is the timeout and nothing else.
797    /// A worker whose shutdown must be observed waits this way rather
798    /// than parking on the next request forever.
799    #[cfg(any(
800        target_os = "linux",
801        target_os = "freebsd",
802        target_os = "macos"
803    ))]
804    pub fn take_offer_timeout(
805        &self,
806        timeout: Duration,
807    ) -> Result<Option<Ticket>> {
808        let deadline = Instant::now() + timeout;
809        loop {
810            if let Some(ticket) = self.take_offer() {
811                return Ok(Some(ticket));
812            }
813            let left = deadline.saturating_duration_since(Instant::now());
814            if left.is_zero() {
815                return Ok(self.take_offer());
816            }
817            if !self.table.wait_offer_timeout(left)? {
818                return Ok(self.take_offer());
819            }
820        }
821    }
822
823    /// Park the thread until a stream is offered to this node.
824    pub fn blocking_take_offer(&self) -> Result<Ticket> {
825        loop {
826            if let Some(ticket) = self.take_offer() {
827                return Ok(ticket);
828            }
829            self.table.wait_offer()?;
830        }
831    }
832
833    /// Readiness for a task: the next offer, or the waker is registered
834    /// and `Pending` comes back.
835    pub fn poll_take_offer(
836        &self,
837        cx: &mut std::task::Context<'_>,
838    ) -> std::task::Poll<Result<Ticket>> {
839        if let Some(ticket) = self.take_offer() {
840            return std::task::Poll::Ready(Ok(ticket));
841        }
842        if let Err(error) = self.table.register_offer(cx.waker()) {
843            return std::task::Poll::Ready(Err(error));
844        }
845        match self.take_offer() {
846            Some(ticket) => std::task::Poll::Ready(Ok(ticket)),
847            None => std::task::Poll::Pending,
848        }
849    }
850
851    /// Every process generation that still holds a side of a stream here.
852    ///
853    /// The mirror of `Pool::owners`, and for the same caller: a supervisor
854    /// that has to decide what to report dead after losing its own record.
855    /// It says who is *in* the table, never who is alive.
856    pub fn owners(&self) -> Vec<(NodeId, Incarnation)> {
857        let mut seen: Vec<(NodeId, Incarnation)> = Vec::new();
858        for slot in self.table.slots() {
859            if slot.state.load(Ordering::Acquire) != SLOT_LIVE {
860                continue;
861            }
862            for side in 0..2 {
863                if slot.claimed[side].load(Ordering::Acquire) != SIDE_CLAIMED {
864                    continue;
865                }
866                let holder = (
867                    NodeId::new(slot.node[side].load(Ordering::Acquire)),
868                    Incarnation::new(
869                        slot.incarnation[side].load(Ordering::Acquire),
870                    ),
871                );
872                if !seen.contains(&holder) {
873                    seen.push(holder);
874                }
875            }
876        }
877        seen.sort_by_key(|(node, incarnation)| (node.get(), incarnation.get()));
878        seen
879    }
880
881    /// A confirmed death, reported by whoever supervises processes: every
882    /// side that incarnation of `node` held, in any lane, is finished. Its
883    /// unfinished writes become resets and its reads are gone, so a holder
884    /// of the other side wakes with an error; that holder keeps the slot
885    /// until it drops its handles, and only then is the slot reused. A
886    /// report for another incarnation of the same node touches nothing.
887    pub fn node_dead(
888        &self,
889        node: NodeId,
890        incarnation: Incarnation,
891    ) {
892        self.table.node_dead(node.get(), incarnation.get());
893    }
894
895    /// This process took over its node from an earlier life: every side that
896    /// any other incarnation of this node held, in any lane, is finished, as
897    /// [`Streams::node_dead`] finishes one. The node id is inherited and the
898    /// incarnation is not, so what the node left behind is told apart from
899    /// this process's own by the stamp alone — no report from a supervisor
900    /// is needed.
901    ///
902    /// Call it once, before this process creates or opens anything. It is
903    /// sound only where a node is handed on after its holder is gone; a
904    /// node given to a second process while the first still runs would
905    /// have that process's streams ended under it.
906    pub fn supersede(&self) {
907        self.table.supersede();
908    }
909
910    /// Clear the table during quiescent owner boot and start a new epoch.
911    /// Every handle and every ticket from before goes stale.
912    pub fn reset_all(&self) {
913        self.table.reset_all();
914    }
915
916    fn ticket_for(
917        &self,
918        index: usize,
919    ) -> Option<Ticket> {
920        let slot = &self.table.slots()[index];
921        let generation = slot.generation.load(Ordering::Acquire);
922        if !slot.is(generation) {
923            return None;
924        }
925        let lane_capacity = self.table.geometry().lane_capacity;
926        let id = StreamId::make(
927            self.table.kind(),
928            (index / lane_capacity) as u16,
929            (index % lane_capacity) as u32,
930            generation,
931        );
932        Some(Ticket { id, side: Side::B, epoch: self.table.epoch() })
933    }
934
935    /// Remove the SHM object. Existing mappings stay valid until their
936    /// processes release them.
937    #[cfg(unix)]
938    pub fn unlink(&self) -> Result<()> {
939        self.table.unlink()
940    }
941
942    fn locate(
943        &self,
944        id: StreamId,
945    ) -> Result<usize> {
946        let geometry = self.table.geometry();
947        if id.kind() != self.table.kind()
948            || usize::from(id.node()) >= geometry.fleet_capacity
949            || id.slot() as usize >= geometry.lane_capacity
950            || id.generation() == 0
951            || id.generation() > GENERATION_MASK
952        {
953            return Err(Error::Malformed(id.to_string()));
954        }
955        Ok(usize::from(id.node()) * geometry.lane_capacity + id.slot() as usize)
956    }
957}
958
959/// One held side. Dropped when both halves are gone; that releases the side.
960struct Handle {
961    table: Arc<Table>,
962    index: usize,
963    id: StreamId,
964    side: Side,
965}
966
967impl Handle {
968    /// The other side's doorbell when `ring`, nobody's otherwise. Everything a
969    /// handle changes is news to its peer and never to itself; see
970    /// [`table::Ring`].
971    fn peer_if(
972        &self,
973        ring: bool,
974    ) -> table::Ring {
975        if ring {
976            table::Ring::Side(1 - self.side.index())
977        } else {
978            table::Ring::Nobody
979        }
980    }
981
982    fn slot(&self) -> Result<&Slot> {
983        let slot = &self.table.slots()[self.index];
984        if slot.is(self.id.generation()) {
985            Ok(slot)
986        } else {
987            Err(Error::Stale(self.id))
988        }
989    }
990
991    fn direction(
992        slot: &Slot,
993        direction: usize,
994    ) -> &Direction {
995        &slot.directions[direction]
996    }
997
998    /// One attempt at the direction this side writes into.
999    fn try_write(
1000        &self,
1001        buf: &[u8],
1002    ) -> Result<usize> {
1003        let slot = self.slot()?;
1004        let direction = Self::direction(slot, self.side.write_direction());
1005        let flags = direction.flags();
1006        if flags & FLAG_RESET != 0 {
1007            return Err(Error::Reset);
1008        }
1009        if flags & FLAG_FIN != 0 {
1010            return Err(Error::Closed);
1011        }
1012        if flags & FLAG_READER_GONE != 0 {
1013            return Err(Error::PeerGone);
1014        }
1015        let buffer_bytes = self.table.geometry().buffer_bytes;
1016        let head = direction.head.load(Ordering::Relaxed);
1017        let tail = direction.tail.load(Ordering::Acquire);
1018        let free = buffer_bytes - (head - tail) as usize;
1019        if buf.is_empty() {
1020            return Ok(0);
1021        }
1022        if free == 0 {
1023            return Err(Error::WouldBlock);
1024        }
1025        let len = buf.len().min(free);
1026        let base = self.table.buffer(self.index, self.side.write_direction());
1027        let start = (head as usize) & (buffer_bytes - 1);
1028        let first = len.min(buffer_bytes - start);
1029        // SAFETY: only this side writes this direction, and `[head, head+len)`
1030        // is free space the reader will not touch until `head` moves.
1031        unsafe {
1032            std::ptr::copy_nonoverlapping(buf.as_ptr(), base.add(start), first);
1033            std::ptr::copy_nonoverlapping(
1034                buf.as_ptr().add(first),
1035                base,
1036                len - first,
1037            );
1038        }
1039        direction.head.store(head + len as u64, Ordering::SeqCst);
1040        // A reader parks only on an empty ring: it looks, registers, looks
1041        // again. Whether one could be parked is the tail *after* this
1042        // commit, never the one read before the copy — the reader may have
1043        // drained the ring and parked while we were copying into it, and
1044        // that older tail would say "nobody to wake" about a task asleep on
1045        // these very bytes. Each side orders its own store before the
1046        // other's load, so at least one of the two sees the other: the
1047        // reader sees the new head and does not park, or this sees the
1048        // drained tail and rings.
1049        std::sync::atomic::fence(Ordering::SeqCst);
1050        let drained = direction.tail.load(Ordering::SeqCst) >= head;
1051        self.table.notify(self.index, slot, direction, self.peer_if(drained));
1052        Ok(len)
1053    }
1054
1055    /// Publish `buf` as one indivisible write, or publish nothing.
1056    fn try_write_exact(
1057        &self,
1058        buf: &[u8],
1059    ) -> Result<()> {
1060        let slot = self.slot()?;
1061        let direction = Self::direction(slot, self.side.write_direction());
1062        let flags = direction.flags();
1063        if flags & FLAG_RESET != 0 {
1064            return Err(Error::Reset);
1065        }
1066        if flags & FLAG_FIN != 0 {
1067            return Err(Error::Closed);
1068        }
1069        if flags & FLAG_READER_GONE != 0 {
1070            return Err(Error::PeerGone);
1071        }
1072        let buffer_bytes = self.table.geometry().buffer_bytes;
1073        if buf.len() > buffer_bytes {
1074            return Err(Error::Malformed(format!(
1075                "atomic stream write is {} bytes; ring capacity is {buffer_bytes}",
1076                buf.len()
1077            )));
1078        }
1079        if buf.is_empty() {
1080            return Ok(());
1081        }
1082        let head = direction.head.load(Ordering::Relaxed);
1083        let tail = direction.tail.load(Ordering::Acquire);
1084        if buffer_bytes - ((head - tail) as usize) < buf.len() {
1085            return Err(Error::WouldBlock);
1086        }
1087        let base = self.table.buffer(self.index, self.side.write_direction());
1088        let start = (head as usize) & (buffer_bytes - 1);
1089        let first = buf.len().min(buffer_bytes - start);
1090        // SAFETY: the complete frame fits in free ring space owned by this writer.
1091        unsafe {
1092            std::ptr::copy_nonoverlapping(buf.as_ptr(), base.add(start), first);
1093            std::ptr::copy_nonoverlapping(
1094                buf.as_ptr().add(first),
1095                base,
1096                buf.len() - first,
1097            );
1098        }
1099        direction.head.store(head + buf.len() as u64, Ordering::SeqCst);
1100        std::sync::atomic::fence(Ordering::SeqCst);
1101        let drained = direction.tail.load(Ordering::SeqCst) >= head;
1102        self.table.notify(self.index, slot, direction, self.peer_if(drained));
1103        Ok(())
1104    }
1105
1106    /// One attempt at the direction this side reads from. `Ok(0)` with a
1107    /// non-empty buffer is clean end of stream.
1108    fn try_read(
1109        &self,
1110        buf: &mut [u8],
1111    ) -> Result<usize> {
1112        let slot = self.slot()?;
1113        let direction = Self::direction(slot, self.side.read_direction());
1114        let flags = direction.flags();
1115        if flags & FLAG_RESET != 0 {
1116            return Err(Error::Reset);
1117        }
1118        let head = direction.head.load(Ordering::Acquire);
1119        let tail = direction.tail.load(Ordering::Relaxed);
1120        let available = (head - tail) as usize;
1121        if buf.is_empty() {
1122            return Ok(0);
1123        }
1124        if available == 0 {
1125            if flags & FLAG_FIN != 0 {
1126                return Ok(0);
1127            }
1128            return Err(Error::WouldBlock);
1129        }
1130        let len = buf.len().min(available);
1131        let base = self.table.buffer(self.index, self.side.read_direction());
1132        let buffer_bytes = self.table.geometry().buffer_bytes;
1133        let start = (tail as usize) & (buffer_bytes - 1);
1134        let first = len.min(buffer_bytes - start);
1135        // SAFETY: `[tail, head)` was published by the writer's `Release`
1136        // store of `head`, acquired above, and is not rewritten until `tail`
1137        // moves past it.
1138        unsafe {
1139            std::ptr::copy_nonoverlapping(
1140                base.add(start),
1141                buf.as_mut_ptr(),
1142                first,
1143            );
1144            std::ptr::copy_nonoverlapping(
1145                base,
1146                buf.as_mut_ptr().add(first),
1147                len - first,
1148            );
1149        }
1150        direction.tail.store(tail + len as u64, Ordering::SeqCst);
1151        // The mirror image, and the other half of that agreement: a writer
1152        // parks only on a full ring, so the head read after this consume is
1153        // what says whether one is parked on the space just freed.
1154        std::sync::atomic::fence(Ordering::SeqCst);
1155        let head_now = direction.head.load(Ordering::SeqCst);
1156        let was_full = head_now - tail >= buffer_bytes as u64;
1157        self.table.notify(self.index, slot, direction, self.peer_if(was_full));
1158        Ok(len)
1159    }
1160
1161    /// Consume exactly one complete frame, or consume nothing.
1162    fn try_read_exact(
1163        &self,
1164        buf: &mut [u8],
1165    ) -> Result<()> {
1166        let slot = self.slot()?;
1167        let direction = Self::direction(slot, self.side.read_direction());
1168        let flags = direction.flags();
1169        if flags & FLAG_RESET != 0 {
1170            return Err(Error::Reset);
1171        }
1172        let buffer_bytes = self.table.geometry().buffer_bytes;
1173        if buf.len() > buffer_bytes {
1174            return Err(Error::Malformed(format!(
1175                "atomic stream read is {} bytes; ring capacity is {buffer_bytes}",
1176                buf.len()
1177            )));
1178        }
1179        if buf.is_empty() {
1180            return Ok(());
1181        }
1182        let head = direction.head.load(Ordering::Acquire);
1183        let tail = direction.tail.load(Ordering::Relaxed);
1184        let available = (head - tail) as usize;
1185        if available < buf.len() {
1186            if flags & FLAG_FIN != 0 {
1187                return Err(Error::Malformed(format!(
1188                    "stream ended with {available} bytes of a {} byte frame",
1189                    buf.len()
1190                )));
1191            }
1192            return Err(Error::WouldBlock);
1193        }
1194        let base = self.table.buffer(self.index, self.side.read_direction());
1195        let start = (tail as usize) & (buffer_bytes - 1);
1196        let first = buf.len().min(buffer_bytes - start);
1197        // SAFETY: the complete frame is within the writer-published range.
1198        unsafe {
1199            std::ptr::copy_nonoverlapping(
1200                base.add(start),
1201                buf.as_mut_ptr(),
1202                first,
1203            );
1204            std::ptr::copy_nonoverlapping(
1205                base,
1206                buf.as_mut_ptr().add(first),
1207                buf.len() - first,
1208            );
1209        }
1210        direction.tail.store(tail + buf.len() as u64, Ordering::SeqCst);
1211        std::sync::atomic::fence(Ordering::SeqCst);
1212        let head_now = direction.head.load(Ordering::SeqCst);
1213        let was_full = head_now - tail >= buffer_bytes as u64;
1214        self.table.notify(self.index, slot, direction, self.peer_if(was_full));
1215        Ok(())
1216    }
1217
1218    fn set_flag(
1219        &self,
1220        direction_index: usize,
1221        flag: u8,
1222    ) {
1223        if let Ok(slot) = self.slot() {
1224            let direction = Self::direction(slot, direction_index);
1225            let before = direction.flags.fetch_or(flag, Ordering::SeqCst);
1226            if before & flag == 0 {
1227                let ring = flag != FLAG_READER_GONE
1228                    || writer_may_park(direction, before);
1229                self.table.notify(
1230                    self.index,
1231                    slot,
1232                    direction,
1233                    self.peer_if(ring),
1234                );
1235            }
1236        }
1237    }
1238
1239    /// Park until `ready` holds for the direction, or the stream ends.
1240    /// The same wait, bounded: `false` is the timeout and nothing else.
1241    #[cfg(any(
1242        target_os = "linux",
1243        target_os = "freebsd",
1244        target_os = "macos"
1245    ))]
1246    fn wait_until_timeout(
1247        &self,
1248        direction_index: usize,
1249        ready: impl Fn(&Direction) -> bool,
1250        timeout: Duration,
1251    ) -> Result<bool> {
1252        let deadline = Instant::now() + timeout;
1253        loop {
1254            let slot = self.slot()?;
1255            let direction = Self::direction(slot, direction_index);
1256            if ready(direction) {
1257                return Ok(true);
1258            }
1259            let left = deadline.saturating_duration_since(Instant::now());
1260            if left.is_zero() {
1261                return Ok(false);
1262            }
1263            direction.waiters.fetch_add(1, Ordering::SeqCst);
1264            let since = direction.changes.load(Ordering::SeqCst);
1265            let outcome = if ready(direction) {
1266                Ok(true)
1267            } else {
1268                wait_on_timeout(&direction.changes, since, left)
1269            };
1270            direction.waiters.fetch_sub(1, Ordering::SeqCst);
1271            if !outcome? {
1272                // The deadline passed; one last look, since readiness may
1273                // have landed between the wait giving up and this line.
1274                let slot = self.slot()?;
1275                return Ok(ready(Self::direction(slot, direction_index)));
1276            }
1277        }
1278    }
1279
1280    fn wait_until(
1281        &self,
1282        direction_index: usize,
1283        ready: impl Fn(&Direction) -> bool,
1284    ) -> Result<()> {
1285        loop {
1286            let slot = self.slot()?;
1287            let direction = Self::direction(slot, direction_index);
1288            if ready(direction) {
1289                return Ok(());
1290            }
1291            direction.waiters.fetch_add(1, Ordering::SeqCst);
1292            let since = direction.changes.load(Ordering::SeqCst);
1293            let outcome = if ready(direction) {
1294                Ok(())
1295            } else {
1296                wait_on(&direction.changes, since)
1297            };
1298            direction.waiters.fetch_sub(1, Ordering::SeqCst);
1299            outcome?;
1300        }
1301    }
1302
1303    /// Check, register, check again: a change between the first look and
1304    /// the registration is not missed.
1305    fn poll_ready(
1306        &self,
1307        interest: Interest,
1308        ready: impl Fn(&Direction) -> bool,
1309        cx: &mut std::task::Context<'_>,
1310    ) -> std::task::Poll<Result<()>> {
1311        let slot = match self.slot() {
1312            Ok(slot) => slot,
1313            Err(error) => return std::task::Poll::Ready(Err(error)),
1314        };
1315        let direction = Self::direction(slot, interest.direction);
1316        if ready(direction) {
1317            return std::task::Poll::Ready(Ok(()));
1318        }
1319        if let Err(error) =
1320            self.table.register(self.index, interest, cx.waker())
1321        {
1322            return std::task::Poll::Ready(Err(error));
1323        }
1324        // Pairs with the fence in `writer_may_park`: a flag set without a
1325        // doorbell is one this second look is guaranteed to see.
1326        std::sync::atomic::fence(Ordering::SeqCst);
1327        if ready(direction) {
1328            std::task::Poll::Ready(Ok(()))
1329        } else {
1330            std::task::Poll::Pending
1331        }
1332    }
1333}
1334
1335/// Whether a task could be parked writing into `direction` as its reader
1336/// goes — the only case in which the reader going has to ring a doorbell.
1337///
1338/// Not a writer that has finished: it will never write or wait again. And not
1339/// while nothing is queued: with the ring empty every write fits, so nobody is
1340/// waiting for room. The ordinary end of an exchange is both — the reader has
1341/// taken everything, and often the writer has already said so — and ringing
1342/// there woke two drivers per exchange for no one.
1343///
1344/// The fence pairs with the one in [`Handle::poll_ready`]: either this sees
1345/// bytes a writer queued before parking, or that writer's second look sees
1346/// the flag. The reader is gone, so `tail` no longer moves, and a ring seen
1347/// empty here fills only through writes that will see the flag first.
1348fn writer_may_park(
1349    direction: &Direction,
1350    before: u8,
1351) -> bool {
1352    if before & (FLAG_FIN | FLAG_RESET) != 0 {
1353        return false;
1354    }
1355    std::sync::atomic::fence(Ordering::SeqCst);
1356    direction.head.load(Ordering::SeqCst)
1357        != direction.tail.load(Ordering::SeqCst)
1358}
1359
1360fn readable(direction: &Direction) -> bool {
1361    direction.head.load(Ordering::Acquire)
1362        != direction.tail.load(Ordering::Relaxed)
1363        || direction.flags() & (FLAG_FIN | FLAG_RESET) != 0
1364}
1365
1366fn writable(
1367    direction: &Direction,
1368    buffer_bytes: usize,
1369) -> bool {
1370    let queued = (direction.head.load(Ordering::Relaxed)
1371        - direction.tail.load(Ordering::Acquire)) as usize;
1372    queued < buffer_bytes
1373        || direction.flags() & (FLAG_FIN | FLAG_RESET | FLAG_READER_GONE) != 0
1374}
1375
1376fn writable_exact(
1377    direction: &Direction,
1378    buffer_bytes: usize,
1379    needed: usize,
1380) -> bool {
1381    let queued = (direction.head.load(Ordering::Relaxed)
1382        - direction.tail.load(Ordering::Acquire)) as usize;
1383    buffer_bytes.saturating_sub(queued) >= needed
1384        || direction.flags() & (FLAG_FIN | FLAG_RESET | FLAG_READER_GONE) != 0
1385}
1386
1387impl Drop for Handle {
1388    /// Both halves are gone: give the side back. The slot empties once no
1389    /// side holds it any more, and every address to this generation goes
1390    /// stale.
1391    fn drop(&mut self) {
1392        let slot = &self.table.slots()[self.index];
1393        if !slot.is(self.id.generation()) {
1394            return;
1395        }
1396        // Only a side that is still ours is ours to release: a death
1397        // report may have finished it already.
1398        if slot.claimed[self.side.index()]
1399            .compare_exchange(
1400                SIDE_CLAIMED,
1401                SIDE_RELEASED,
1402                Ordering::SeqCst,
1403                Ordering::SeqCst,
1404            )
1405            .is_err()
1406        {
1407            return;
1408        }
1409        // Side A is claimed at creation, so a free B was simply never
1410        // taken; a released or dead side is gone. Only a claimed side keeps
1411        // the slot alive.
1412        let other = slot.claimed[1 - self.side.index()].load(Ordering::SeqCst);
1413        if other != SIDE_CLAIMED {
1414            let _ = slot.state.compare_exchange(
1415                SLOT_LIVE,
1416                SLOT_EMPTY,
1417                Ordering::SeqCst,
1418                Ordering::SeqCst,
1419            );
1420            for direction in &slot.directions {
1421                // A parked waiter with a handle to this generation wakes
1422                // and finds it stale.
1423                direction.changes.fetch_add(1, Ordering::SeqCst);
1424                if direction.waiters.load(Ordering::SeqCst) > 0 {
1425                    wake_on(&direction.changes);
1426                }
1427            }
1428            self.table.registry.wake(self.index);
1429        }
1430    }
1431}
1432
1433/// Both ends of one side: what [`Streams::create`] and [`Streams::open`]
1434/// return. Reads and writes go through it directly, or [`Endpoint::split`]
1435/// gives the halves to two tasks.
1436pub struct Endpoint {
1437    read: ReadHalf,
1438    write: WriteHalf,
1439}
1440
1441impl Endpoint {
1442    fn new(handle: Arc<Handle>) -> Self {
1443        Self {
1444            read: ReadHalf { handle: Arc::clone(&handle) },
1445            write: WriteHalf { handle, shut: false },
1446        }
1447    }
1448
1449    pub fn id(&self) -> StreamId {
1450        self.read.handle.id
1451    }
1452
1453    pub fn side(&self) -> Side {
1454        self.read.handle.side
1455    }
1456
1457    pub fn split(self) -> (ReadHalf, WriteHalf) {
1458        (self.read, self.write)
1459    }
1460
1461    /// Put a split endpoint back together.
1462    ///
1463    /// For a caller that keeps its streams between exchanges: the halves go
1464    /// out to whoever pumps the bodies and come back here, so the slot is
1465    /// never given up and never claimed again. Neither half is dropped on the
1466    /// way through, so neither terminal flag is set.
1467    ///
1468    /// Refused if the two halves are not the same side of the same stream,
1469    /// which would otherwise build an endpoint that reads one slot and writes
1470    /// another.
1471    pub fn rejoin(
1472        read: ReadHalf,
1473        write: WriteHalf,
1474    ) -> Result<Self> {
1475        if read.id() != write.id() {
1476            return Err(Error::Stale(read.id()));
1477        }
1478        Ok(Self { read, write })
1479    }
1480
1481    /// Clear this creator-held endpoint for another peer and restore its
1482    /// process-local write state.
1483    ///
1484    /// [`Streams::rearm`] clears the shared directions after side B has let
1485    /// go. An endpoint that sent FIN also remembers that fact locally in its
1486    /// write half, so reusing the slot through the endpoint needs to clear
1487    /// both layers. Keeping that pairing here prevents a caller from
1488    /// successfully rearming SHM and then finding its retained writer still
1489    /// closed.
1490    ///
1491    /// Only side A, the creator-held side, is reusable. The ticket remains
1492    /// valid because rearming does not change the stream generation.
1493    pub fn rearm(
1494        &mut self,
1495        streams: &Streams,
1496    ) -> Result<()> {
1497        if self.side() != Side::A {
1498            return Err(Error::Malformed(
1499                "only the creator side of a stream can be rearmed".to_owned(),
1500            ));
1501        }
1502        streams.rearm(self.id())?;
1503        self.write.shut = false;
1504        Ok(())
1505    }
1506
1507    pub fn try_read(
1508        &self,
1509        buf: &mut [u8],
1510    ) -> Result<usize> {
1511        self.read.try_read(buf)
1512    }
1513
1514    pub fn try_read_exact(
1515        &self,
1516        buf: &mut [u8],
1517    ) -> Result<()> {
1518        self.read.try_read_exact(buf)
1519    }
1520
1521    pub fn blocking_read(
1522        &self,
1523        buf: &mut [u8],
1524    ) -> Result<usize> {
1525        self.read.blocking_read(buf)
1526    }
1527
1528    pub fn blocking_read_chunk(
1529        &self,
1530        max: usize,
1531    ) -> Result<Bytes> {
1532        self.read.blocking_read_chunk(max)
1533    }
1534
1535    pub fn try_write(
1536        &self,
1537        buf: &[u8],
1538    ) -> Result<usize> {
1539        self.write.try_write(buf)
1540    }
1541
1542    pub fn try_write_exact(
1543        &self,
1544        buf: &[u8],
1545    ) -> Result<()> {
1546        self.write.try_write_exact(buf)
1547    }
1548
1549    pub fn blocking_write(
1550        &self,
1551        buf: &[u8],
1552    ) -> Result<usize> {
1553        self.write.blocking_write(buf)
1554    }
1555
1556    pub fn blocking_write_all(
1557        &self,
1558        buf: &[u8],
1559    ) -> Result<()> {
1560        self.write.blocking_write_all(buf)
1561    }
1562
1563    pub fn finish(&mut self) -> Result<()> {
1564        self.write.finish()
1565    }
1566
1567    pub fn reset(&mut self) {
1568        self.write.reset()
1569    }
1570}
1571
1572/// The reading end of one side. Dropping it tells the peer's writer that
1573/// nothing more will be read.
1574pub struct ReadHalf {
1575    handle: Arc<Handle>,
1576}
1577
1578/// A reset-only observation of a reading direction, without consuming bytes
1579/// or treating a clean FIN as failure. One reset observer per direction has a
1580/// separate bounded local waker; it cannot replace the body reader's waker.
1581pub struct ResetSignal {
1582    handle: Arc<Handle>,
1583}
1584
1585impl ResetSignal {
1586    /// Await only RESET (or an invalidated stream generation). Buffered data
1587    /// and FIN do not complete this observation. No timer or new driver is used.
1588    pub fn poll_reset(
1589        &self,
1590        cx: &mut std::task::Context<'_>,
1591    ) -> std::task::Poll<Result<()>> {
1592        self.handle.poll_ready(
1593            Interest {
1594                direction: self.handle.side.read_direction(),
1595                writer: false,
1596                reset: true,
1597            },
1598            |direction| direction.flags() & FLAG_RESET != 0,
1599            cx,
1600        )
1601    }
1602}
1603
1604impl ReadHalf {
1605    /// Observe an aborted response while another channel carries its metadata.
1606    /// The signal owns no reader role and does not stop reading when dropped.
1607    pub fn reset_signal(&self) -> ResetSignal {
1608        ResetSignal { handle: Arc::clone(&self.handle) }
1609    }
1610
1611    pub fn id(&self) -> StreamId {
1612        self.handle.id
1613    }
1614
1615    /// Read what is there now. `Ok(0)` is clean end of stream;
1616    /// [`Error::WouldBlock`] means nothing yet.
1617    pub fn try_read(
1618        &self,
1619        buf: &mut [u8],
1620    ) -> Result<usize> {
1621        self.handle.try_read(buf)
1622    }
1623
1624    /// Read the complete buffer atomically. When it is not all available,
1625    /// returns [`Error::WouldBlock`] without consuming a prefix.
1626    pub fn try_read_exact(
1627        &self,
1628        buf: &mut [u8],
1629    ) -> Result<()> {
1630        self.handle.try_read_exact(buf)
1631    }
1632
1633    /// Park until something can be read, then read it. `Ok(0)` is clean
1634    /// end of stream. Blocks the thread; an async runtime uses `AsyncRead`.
1635    pub fn blocking_read(
1636        &self,
1637        buf: &mut [u8],
1638    ) -> Result<usize> {
1639        loop {
1640            match self.handle.try_read(buf) {
1641                Err(Error::WouldBlock) => self.wait_readable()?,
1642                other => return other,
1643            }
1644        }
1645    }
1646
1647    /// Tell the peer's writer that nothing more will be read, without letting
1648    /// go of the stream.
1649    ///
1650    /// What dropping this half says, for a holder that keeps its halves
1651    /// between exchanges. Without it a writer the reader has given up on —
1652    /// a client gone mid-response — waits on a full ring that nobody will
1653    /// empty. Re-arming the stream clears it again.
1654    pub fn stop_reading(&self) {
1655        self.handle
1656            .set_flag(self.handle.side.read_direction(), FLAG_READER_GONE);
1657    }
1658
1659    /// Up to `max` bytes as one owned chunk; empty at clean end of stream.
1660    pub fn blocking_read_chunk(
1661        &self,
1662        max: usize,
1663    ) -> Result<Bytes> {
1664        let mut chunk = BytesMut::zeroed(max);
1665        let len = self.blocking_read(&mut chunk)?;
1666        chunk.truncate(len);
1667        Ok(chunk.freeze())
1668    }
1669
1670    /// The same wait, bounded: `false` is the timeout and nothing else.
1671    #[cfg(any(
1672        target_os = "linux",
1673        target_os = "freebsd",
1674        target_os = "macos"
1675    ))]
1676    pub fn wait_readable_timeout(
1677        &self,
1678        timeout: Duration,
1679    ) -> Result<bool> {
1680        self.handle.wait_until_timeout(
1681            self.handle.side.read_direction(),
1682            readable,
1683            timeout,
1684        )
1685    }
1686
1687    /// Park until a read would make progress or the direction ended.
1688    pub fn wait_readable(&self) -> Result<()> {
1689        self.handle.wait_until(self.handle.side.read_direction(), readable)
1690    }
1691
1692    /// Readiness for a task: `Ready` when a read would make progress or the
1693    /// direction ended, otherwise the waker is registered and `Pending`
1694    /// comes back. Runtime-neutral; the `tokio` feature builds `AsyncRead`
1695    /// on it.
1696    pub fn poll_readable(
1697        &self,
1698        cx: &mut std::task::Context<'_>,
1699    ) -> std::task::Poll<Result<()>> {
1700        self.handle.poll_ready(
1701            Interest {
1702                direction: self.handle.side.read_direction(),
1703                writer: false,
1704                reset: false,
1705            },
1706            readable,
1707            cx,
1708        )
1709    }
1710}
1711
1712impl Drop for ReadHalf {
1713    fn drop(&mut self) {
1714        self.handle
1715            .set_flag(self.handle.side.read_direction(), FLAG_READER_GONE);
1716    }
1717}
1718
1719/// The writing end of one side. Dropping it without [`WriteHalf::finish`]
1720/// resets the direction, as a dropped socket would.
1721pub struct WriteHalf {
1722    handle: Arc<Handle>,
1723    shut: bool,
1724}
1725
1726impl WriteHalf {
1727    /// Wait for the peer's confirmed node death, even while no bytes are
1728    /// being produced. Ordinary FIN and endpoint release are not death.
1729    ///
1730    /// Shares the writer's readiness registration: poll this from the same
1731    /// task as writes, not from a second concurrent writer task.
1732    pub fn poll_peer_dead(
1733        &self,
1734        cx: &mut std::task::Context<'_>,
1735    ) -> std::task::Poll<Result<()>> {
1736        let slot = match self.handle.slot() {
1737            Ok(slot) => slot,
1738            Err(error) => return std::task::Poll::Ready(Err(error)),
1739        };
1740        let peer = &slot.claimed[1 - self.handle.side.index()];
1741        if peer.load(Ordering::SeqCst) == SIDE_DEAD {
1742            return std::task::Poll::Ready(Ok(()));
1743        }
1744        let interest = Interest {
1745            direction: self.handle.side.write_direction(),
1746            writer: true,
1747            reset: false,
1748        };
1749        if let Err(error) =
1750            self.handle.table.register(self.handle.index, interest, cx.waker())
1751        {
1752            return std::task::Poll::Ready(Err(error));
1753        }
1754        // node_dead always rings both directions. Check after registering so
1755        // a death racing the first check cannot strand this task.
1756        if peer.load(Ordering::SeqCst) == SIDE_DEAD {
1757            std::task::Poll::Ready(Ok(()))
1758        } else {
1759            std::task::Poll::Pending
1760        }
1761    }
1762
1763    pub fn id(&self) -> StreamId {
1764        self.handle.id
1765    }
1766
1767    /// Write what fits now; [`Error::WouldBlock`] when the ring is full.
1768    pub fn try_write(
1769        &self,
1770        buf: &[u8],
1771    ) -> Result<usize> {
1772        self.handle.try_write(buf)
1773    }
1774
1775    /// Write the complete buffer atomically. When it does not all fit,
1776    /// returns [`Error::WouldBlock`] without publishing a prefix.
1777    pub fn try_write_exact(
1778        &self,
1779        buf: &[u8],
1780    ) -> Result<()> {
1781        self.handle.try_write_exact(buf)
1782    }
1783
1784    /// Park until something fits, then write it. Blocks the thread.
1785    pub fn blocking_write(
1786        &self,
1787        buf: &[u8],
1788    ) -> Result<usize> {
1789        loop {
1790            match self.handle.try_write(buf) {
1791                Err(Error::WouldBlock) => self.wait_writable()?,
1792                other => return other,
1793            }
1794        }
1795    }
1796
1797    /// Write everything, waiting as needed. A failure part-way has already
1798    /// committed a prefix; nothing is replayed.
1799    pub fn blocking_write_all(
1800        &self,
1801        mut buf: &[u8],
1802    ) -> Result<()> {
1803        while !buf.is_empty() {
1804            let written = self.blocking_write(buf)?;
1805            buf = &buf[written..];
1806        }
1807        Ok(())
1808    }
1809
1810    /// No more bytes from this side (FIN); what is buffered still reaches
1811    /// the reader, then it sees a clean end. The other direction is
1812    /// unaffected. `AsyncWriteExt::shutdown` does the same.
1813    pub fn finish(&mut self) -> Result<()> {
1814        self.shut = true;
1815        self.handle.set_flag(self.handle.side.write_direction(), FLAG_FIN);
1816        Ok(())
1817    }
1818
1819    /// Abandon the direction: the reader gets an error and buffered bytes
1820    /// are discarded.
1821    pub fn reset(&mut self) {
1822        self.shut = true;
1823        self.handle.set_flag(self.handle.side.write_direction(), FLAG_RESET);
1824    }
1825
1826    /// The same wait, bounded: `false` is the timeout and nothing else,
1827    /// which is how a caller with a deadline of its own gives up without
1828    /// giving up its stream.
1829    #[cfg(any(
1830        target_os = "linux",
1831        target_os = "freebsd",
1832        target_os = "macos"
1833    ))]
1834    pub fn wait_writable_timeout(
1835        &self,
1836        timeout: Duration,
1837    ) -> Result<bool> {
1838        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1839        self.handle.wait_until_timeout(
1840            self.handle.side.write_direction(),
1841            |direction| writable(direction, buffer_bytes),
1842            timeout,
1843        )
1844    }
1845
1846    /// Park until a write would make progress or the direction ended.
1847    pub fn wait_writable(&self) -> Result<()> {
1848        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1849        self.handle
1850            .wait_until(self.handle.side.write_direction(), |direction| {
1851                writable(direction, buffer_bytes)
1852            })
1853    }
1854
1855    /// Park until an atomic write of `needed` bytes can be attempted.
1856    pub fn wait_writable_exact(
1857        &self,
1858        needed: usize,
1859    ) -> Result<()> {
1860        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1861        if needed > buffer_bytes {
1862            return Err(Error::Malformed(format!(
1863                "atomic stream write is {needed} bytes; ring capacity is {buffer_bytes}"
1864            )));
1865        }
1866        self.handle
1867            .wait_until(self.handle.side.write_direction(), |direction| {
1868                writable_exact(direction, buffer_bytes, needed)
1869            })
1870    }
1871
1872    /// Readiness for a task: `Ready` when a write would make progress or
1873    /// the direction ended, otherwise the waker is registered and `Pending`
1874    /// comes back. Runtime-neutral; the `tokio` feature builds `AsyncWrite`
1875    /// on it.
1876    pub fn poll_writable(
1877        &self,
1878        cx: &mut std::task::Context<'_>,
1879    ) -> std::task::Poll<Result<()>> {
1880        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1881        self.handle.poll_ready(
1882            Interest {
1883                direction: self.handle.side.write_direction(),
1884                writer: true,
1885                reset: false,
1886            },
1887            |direction| writable(direction, buffer_bytes),
1888            cx,
1889        )
1890    }
1891
1892    /// Task readiness for an indivisible write of `needed` bytes.
1893    pub fn poll_writable_exact(
1894        &self,
1895        needed: usize,
1896        cx: &mut std::task::Context<'_>,
1897    ) -> std::task::Poll<Result<()>> {
1898        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1899        if needed > buffer_bytes {
1900            return std::task::Poll::Ready(Err(Error::Malformed(format!(
1901                "atomic stream write is {needed} bytes; ring capacity is {buffer_bytes}"
1902            ))));
1903        }
1904        self.handle.poll_ready(
1905            Interest {
1906                direction: self.handle.side.write_direction(),
1907                writer: true,
1908                reset: false,
1909            },
1910            |direction| writable_exact(direction, buffer_bytes, needed),
1911            cx,
1912        )
1913    }
1914}
1915
1916impl Drop for WriteHalf {
1917    fn drop(&mut self) {
1918        if !self.shut {
1919            self.handle
1920                .set_flag(self.handle.side.write_direction(), FLAG_RESET);
1921        }
1922    }
1923}
1924
1925/// Whether this build can park on a shared word. A table refuses to open
1926/// where it cannot: a sleep loop wearing the shape of a wait is worse
1927/// than a clear no, and nothing above here should have to ask again.
1928#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1929pub(crate) fn waits_supported() -> bool {
1930    orbit_core::sync::supported()
1931}
1932
1933#[cfg(not(any(
1934    target_os = "linux",
1935    target_os = "freebsd",
1936    target_os = "macos"
1937)))]
1938pub(crate) fn waits_supported() -> bool {
1939    false
1940}
1941
1942/// Park until `word` no longer holds `expected`, through the platform's
1943/// shared address wait. There is no polling fallback: a table refuses to
1944/// open where the platform cannot wait, so by here it can.
1945#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1946/// Park until the word moves or `timeout` passes. `false` is the
1947/// timeout and nothing else.
1948#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1949pub(crate) fn wait_on_timeout(
1950    word: &AtomicU32,
1951    expected: u32,
1952    timeout: Duration,
1953) -> Result<bool> {
1954    orbit_core::sync::wait_word_timeout(word, expected, timeout)
1955        .map_err(Error::Io)
1956}
1957
1958pub(crate) fn wait_on(
1959    word: &AtomicU32,
1960    expected: u32,
1961) -> Result<()> {
1962    orbit_core::sync::wait_word(word, expected).map_err(Error::Io)
1963}
1964
1965#[cfg(not(any(
1966    target_os = "linux",
1967    target_os = "freebsd",
1968    target_os = "macos"
1969)))]
1970pub(crate) fn wait_on(
1971    _word: &AtomicU32,
1972    _expected: u32,
1973) -> Result<()> {
1974    Err(Error::Io(std::io::Error::new(
1975        std::io::ErrorKind::Unsupported,
1976        "orbit-stream needs a platform that can wait on a shared word",
1977    )))
1978}
1979
1980/// Wake everyone parked on `word`; nothing to do where nobody can park.
1981pub(crate) fn wake_on(word: &AtomicU32) {
1982    #[cfg(any(
1983        target_os = "linux",
1984        target_os = "freebsd",
1985        target_os = "macos"
1986    ))]
1987    let _ = orbit_core::sync::wake_word(word);
1988    #[cfg(not(any(
1989        target_os = "linux",
1990        target_os = "freebsd",
1991        target_os = "macos"
1992    )))]
1993    let _ = word;
1994}
1995
1996pub(crate) fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
1997    mutex.lock().unwrap_or_else(|error| error.into_inner())
1998}
1999
2000#[cfg(test)]
2001mod tests {
2002    use std::sync::Arc;
2003    use std::time::Duration;
2004
2005    use orbit_core::{Fleet, NodeId};
2006
2007    use super::{
2008        Error, Incarnation, STREAM_BUFFER_BYTES, STREAM_LANE_CAPACITY, Side,
2009        Streams, Ticket,
2010    };
2011
2012    fn streams(name: &'static str) -> Streams {
2013        Streams::new(
2014            Arc::new(Fleet::join(name, 2).unwrap()),
2015            Incarnation::new(1),
2016        )
2017        .unwrap()
2018    }
2019
2020    #[test]
2021    fn bytes_cross_in_order_and_end_cleanly() {
2022        let streams = streams("stream-order");
2023        let (mut a, ticket) = streams.create().unwrap();
2024        assert_eq!(ticket.side, Side::B);
2025        assert_eq!(ticket.id.node(), 0);
2026        assert_eq!(ticket.epoch, streams.epoch());
2027        let b = streams.open(ticket).unwrap();
2028
2029        a.blocking_write_all(b"hello, ").unwrap();
2030        a.blocking_write_all(b"world").unwrap();
2031        a.finish().unwrap();
2032
2033        let mut out = Vec::new();
2034        let mut buf = [0_u8; 4];
2035        loop {
2036            let n = b.blocking_read(&mut buf).unwrap();
2037            if n == 0 {
2038                break;
2039            }
2040            out.extend_from_slice(&buf[..n]);
2041        }
2042        assert_eq!(out, b"hello, world");
2043        // The other direction is its own thing.
2044        b.blocking_write_all(b"back").unwrap();
2045        assert_eq!(a.blocking_read_chunk(16).unwrap().as_ref(), b"back");
2046    }
2047
2048    #[test]
2049    fn a_ticket_prints_and_parses() {
2050        let streams = streams("stream-ticket");
2051        let (_a, ticket) = streams.create().unwrap();
2052        let text = ticket.to_string();
2053        assert!(text.contains("/b/"), "{text}");
2054        assert_eq!(text.parse::<Ticket>().unwrap(), ticket);
2055        assert!(matches!("nope".parse::<Ticket>(), Err(Error::Malformed(_))));
2056        assert!(matches!("x/b".parse::<Ticket>(), Err(Error::Malformed(_))));
2057    }
2058
2059    #[test]
2060    fn a_side_is_held_once_and_a_released_stream_goes_stale() {
2061        let streams = streams("stream-claim");
2062        let (a, ticket) = streams.create().unwrap();
2063        let b = streams.open(ticket).unwrap();
2064        assert!(matches!(streams.open(ticket), Err(Error::AlreadyClaimed(_))));
2065        assert!(streams.is_live(ticket.id));
2066
2067        drop(a);
2068        // A's write half went without finishing: B reads a reset.
2069        let mut buf = [0_u8; 1];
2070        assert!(matches!(b.blocking_read(&mut buf), Err(Error::Reset)));
2071        drop(b);
2072        assert!(!streams.is_live(ticket.id));
2073        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
2074
2075        // Once the lane wraps, the slot is reused under a new generation and
2076        // the old ticket stays stale.
2077        for _ in 0..STREAM_LANE_CAPACITY - 1 {
2078            drop(streams.create().unwrap());
2079        }
2080        let (_a2, ticket2) = streams.create().unwrap();
2081        assert_eq!(ticket2.id.slot(), ticket.id.slot());
2082        assert_ne!(ticket2.id.generation(), ticket.id.generation());
2083        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
2084    }
2085
2086    #[test]
2087    fn a_reset_starts_a_new_epoch_and_refuses_old_tickets() {
2088        let streams = streams("stream-epoch");
2089        let (_a, ticket) = streams.create().unwrap();
2090        let before = streams.epoch();
2091        streams.reset_all();
2092        assert!(streams.epoch() > before);
2093        // The slot is free again and reinstalled from generation one, which
2094        // is exactly what the old ticket names; the epoch tells them apart.
2095        let (_a2, ticket2) = streams.create().unwrap();
2096        assert_eq!(ticket2.id, ticket.id);
2097        assert_ne!(ticket2.epoch, ticket.epoch);
2098        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
2099        assert!(streams.open(ticket2).is_ok());
2100    }
2101
2102    #[test]
2103    fn a_slot_whose_generation_runs_out_is_not_reused() {
2104        // GENERATION_LIMIT is 4 under test: each slot can be installed four
2105        // times in one epoch, then it sits out until the next epoch.
2106        let streams = streams("stream-exhaust");
2107        let limit = super::layout::GENERATION_LIMIT as usize;
2108        for _ in 0..STREAM_LANE_CAPACITY * limit {
2109            drop(streams.create().unwrap());
2110        }
2111        assert!(matches!(streams.create(), Err(Error::Full { .. })));
2112        streams.reset_all();
2113        let (_a, ticket) = streams.create().unwrap();
2114        assert_eq!(ticket.id.generation(), 1);
2115    }
2116
2117    #[test]
2118    fn a_full_ring_blocks_the_writer_and_wraps_correctly() {
2119        let streams = streams("stream-wrap");
2120        let (a, ticket) = streams.create().unwrap();
2121        let b = streams.open(ticket).unwrap();
2122
2123        let filler = vec![7_u8; STREAM_BUFFER_BYTES];
2124        assert_eq!(a.try_write(&filler).unwrap(), STREAM_BUFFER_BYTES);
2125        assert!(matches!(a.try_write(b"x"), Err(Error::WouldBlock)));
2126
2127        let mut buf = vec![0_u8; STREAM_BUFFER_BYTES - 5];
2128        assert_eq!(b.blocking_read(&mut buf).unwrap(), STREAM_BUFFER_BYTES - 5);
2129        // Room again; the next write wraps around the end of the ring.
2130        let tail =
2131            (0..STREAM_BUFFER_BYTES).map(|i| i as u8).collect::<Vec<_>>();
2132        assert_eq!(a.try_write(&tail).unwrap(), STREAM_BUFFER_BYTES - 5);
2133
2134        let mut rest = vec![0_u8; 5];
2135        assert_eq!(b.blocking_read(&mut rest).unwrap(), 5);
2136        assert_eq!(rest, vec![7_u8; 5]);
2137        let mut wrapped = vec![0_u8; STREAM_BUFFER_BYTES];
2138        let mut got = 0;
2139        while got < STREAM_BUFFER_BYTES - 5 {
2140            got += b.blocking_read(&mut wrapped[got..]).unwrap();
2141        }
2142        assert_eq!(&wrapped[..got], &tail[..got]);
2143    }
2144
2145    #[test]
2146    fn exact_frames_never_publish_or_consume_a_prefix() {
2147        let streams = streams("stream-exact-frame");
2148        let (a, ticket) = streams.create().unwrap();
2149        let b = streams.open(ticket).unwrap();
2150
2151        let filler = vec![7_u8; STREAM_BUFFER_BYTES - 32];
2152        a.try_write_exact(&filler).unwrap();
2153        assert!(matches!(
2154            a.try_write_exact(&[9_u8; 64]),
2155            Err(Error::WouldBlock)
2156        ));
2157        let mut consumed = vec![0_u8; filler.len()];
2158        b.try_read_exact(&mut consumed).unwrap();
2159        assert_eq!(consumed, filler);
2160        assert!(matches!(b.try_read(&mut [0_u8; 1]), Err(Error::WouldBlock)));
2161
2162        a.try_write_exact(&[3_u8; 32]).unwrap();
2163        assert!(matches!(
2164            b.try_read_exact(&mut [0_u8; 64]),
2165            Err(Error::WouldBlock)
2166        ));
2167        let mut frame = [0_u8; 32];
2168        b.try_read_exact(&mut frame).unwrap();
2169        assert_eq!(frame, [3_u8; 32]);
2170    }
2171
2172    #[test]
2173    fn a_blocked_reader_is_woken_by_a_write_in_another_thread() {
2174        let streams = streams("stream-wake");
2175        let (a, ticket) = streams.create().unwrap();
2176        let b = streams.open(ticket).unwrap();
2177        let reader = std::thread::spawn(move || {
2178            let mut buf = [0_u8; 8];
2179            let n = b.blocking_read(&mut buf).unwrap();
2180            buf[..n].to_vec()
2181        });
2182        std::thread::sleep(Duration::from_millis(30));
2183        a.blocking_write_all(b"ping").unwrap();
2184        assert_eq!(reader.join().unwrap(), b"ping");
2185    }
2186
2187    #[test]
2188    fn a_reader_gone_fails_the_writer_and_a_lane_fills_up() {
2189        let streams = streams("stream-gone");
2190        let (a, ticket) = streams.create().unwrap();
2191        let b = streams.open(ticket).unwrap();
2192        let (b_read, _b_write) = b.split();
2193        drop(b_read);
2194        assert!(matches!(a.blocking_write(b"nobody"), Err(Error::PeerGone)));
2195
2196        let mut held = vec![a];
2197        for _ in 1..STREAM_LANE_CAPACITY {
2198            held.push(streams.create().unwrap().0);
2199        }
2200        assert!(matches!(streams.create(), Err(Error::Full { .. })));
2201    }
2202
2203    #[test]
2204    fn a_death_report_ends_that_incarnations_sides_only() {
2205        let streams = streams("stream-dead");
2206        let (a, ticket) = streams.create().unwrap();
2207        let b = streams.open(ticket).unwrap();
2208        b.blocking_write_all(b"partial").unwrap();
2209
2210        // Another incarnation of the same node: nothing happens.
2211        streams.node_dead(NodeId::ZERO, Incarnation::new(2));
2212        assert_eq!(a.blocking_read_chunk(16).unwrap().as_ref(), b"partial");
2213
2214        // Our incarnation died (as a supervisor would report about a peer):
2215        // both sides here are stamped with it, so both end and the slot
2216        // empties at once. A parked reader wakes with an error either way;
2217        // the two-incarnation case, where the survivor keeps the slot and
2218        // reads a reset, is the shared-memory test.
2219        let reader = std::thread::spawn(move || {
2220            let mut buf = [0_u8; 8];
2221            a.blocking_read(&mut buf).map(|_| ())
2222        });
2223        std::thread::sleep(Duration::from_millis(30));
2224        streams.node_dead(NodeId::ZERO, Incarnation::new(1));
2225        assert!(matches!(
2226            reader.join().unwrap(),
2227            Err(Error::Reset | Error::Stale(_))
2228        ));
2229        assert!(matches!(
2230            b.try_write(b"x"),
2231            Err(Error::Reset | Error::Stale(_))
2232        ));
2233        assert!(matches!(
2234            streams.open(ticket),
2235            Err(Error::AlreadyClaimed(_) | Error::Stale(_))
2236        ));
2237        drop(b);
2238        assert!(!streams.is_live(ticket.id));
2239    }
2240
2241    #[test]
2242    fn an_offer_is_taken_once_and_a_stale_offer_is_skipped() {
2243        let streams = streams("stream-offer");
2244        assert!(streams.take_offer().is_none());
2245        let (a, ticket) = streams.create().unwrap();
2246        streams.offer(ticket, NodeId::ZERO).unwrap();
2247        assert_eq!(streams.take_offer(), Some(ticket));
2248        assert!(streams.take_offer().is_none());
2249
2250        let (gone, stale) = streams.create().unwrap();
2251        streams.offer(stale, NodeId::ZERO).unwrap();
2252        drop(gone);
2253        assert!(streams.take_offer().is_none());
2254
2255        let taker = streams.clone();
2256        let waiter = std::thread::spawn(move || taker.blocking_take_offer());
2257        std::thread::sleep(Duration::from_millis(30));
2258        streams.offer(ticket, NodeId::ZERO).unwrap();
2259        assert_eq!(waiter.join().unwrap().unwrap(), ticket);
2260        drop(a);
2261    }
2262
2263    #[test]
2264    fn a_contested_claim_goes_to_exactly_one_opener() {
2265        let streams = streams("stream-race");
2266        for _ in 0..50 {
2267            let (_a, ticket) = streams.create().unwrap();
2268            let openers = (0..4)
2269                .map(|_| {
2270                    let streams = streams.clone();
2271                    std::thread::spawn(move || streams.open(ticket).is_ok())
2272                })
2273                .collect::<Vec<_>>();
2274            let won = openers
2275                .into_iter()
2276                .map(|opener| opener.join().unwrap())
2277                .filter(|won| *won)
2278                .count();
2279            assert_eq!(won, 1);
2280        }
2281    }
2282
2283    #[test]
2284    fn random_sizes_concatenate_exactly() {
2285        let streams = streams("stream-random");
2286        let (mut a, ticket) = streams.create().unwrap();
2287        let b = streams.open(ticket).unwrap();
2288        let payload = (0..300_000_u32)
2289            .map(|i| (i.wrapping_mul(2654435761) >> 13) as u8)
2290            .collect::<Vec<_>>();
2291        let expected = payload.clone();
2292        let writer = std::thread::spawn(move || {
2293            let mut rest = &payload[..];
2294            let mut step = 1_usize;
2295            while !rest.is_empty() {
2296                let len = step.min(rest.len());
2297                a.blocking_write_all(&rest[..len]).unwrap();
2298                rest = &rest[len..];
2299                step = (step * 7 + 13) % 9_001 + 1;
2300            }
2301            a.finish().unwrap();
2302        });
2303        let mut out = Vec::new();
2304        let mut step = 3_usize;
2305        loop {
2306            let chunk = b.blocking_read_chunk(step).unwrap();
2307            if chunk.is_empty() {
2308                break;
2309            }
2310            out.extend_from_slice(&chunk);
2311            step = (step * 5 + 11) % 4_099 + 1;
2312        }
2313        writer.join().unwrap();
2314        assert_eq!(out, expected);
2315    }
2316}