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    /// Report one confirmed death to an existing shared table without
445    /// claiming an endpoint, superseding a node, or installing a driver.
446    /// Returns false when the table does not exist; never creates it then.
447    #[cfg(unix)]
448    pub fn node_dead_in_shm(
449        name: &str,
450        fleet_capacity: u16,
451        spec: StreamSpec,
452        node: NodeId,
453        incarnation: Incarnation,
454    ) -> Result<bool> {
455        spec.validate()?;
456        if node.get() >= fleet_capacity {
457            return Err(Error::Malformed(
458                "recovery node is outside the fleet".into(),
459            ));
460        }
461        let segment = orbit_core::shm::ring_segment_name(name, spec.kind);
462        match orbit_core::shm::ShmRegion::validate_existing(
463            &segment,
464            segment_size_for(fleet_capacity, spec),
465        )? {
466            orbit_core::shm::ShmValidation::Missing => Ok(false),
467            orbit_core::shm::ShmValidation::Valid { .. } => {
468                table::recover_shm(
469                    name,
470                    fleet_capacity,
471                    spec,
472                    node.get(),
473                    incarnation.get(),
474                )?;
475                Ok(true)
476            }
477        }
478    }
479
480    /// Open the fleet's table as this process's incarnation. Every side
481    /// this process claims is stamped with it, and [`Streams::node_dead`]
482    /// for the same node and incarnation is what ends those sides.
483    pub fn new(
484        fleet: Arc<Fleet>,
485        incarnation: Incarnation,
486    ) -> Result<Self> {
487        Self::with_spec(fleet, incarnation, StreamSpec::DEFAULT)
488    }
489
490    /// Open the table `spec` names. Independent specs are independent
491    /// tables: separate segments, separate lanes and rings, separate
492    /// epochs, and a `reset_all` on one leaves the others alone. A process
493    /// may hold as many as it has specs, but each under one incarnation.
494    pub fn with_spec(
495        fleet: Arc<Fleet>,
496        incarnation: Incarnation,
497        spec: StreamSpec,
498    ) -> Result<Self> {
499        spec.validate()?;
500        Ok(Self { table: table::open(&fleet, incarnation, spec)? })
501    }
502
503    /// The kind this table's segment lives under.
504    pub fn kind(&self) -> u8 {
505        self.table.kind()
506    }
507
508    /// A descriptor that becomes readable when something among this
509    /// process's streams may have changed, for a runtime that parks on
510    /// descriptors rather than on wakers.
511    ///
512    /// Edge-triggered and coalescing: drain it, then re-try the
513    /// non-blocking calls on the streams you hold. It is what makes this
514    /// table usable from a foreign event loop — an embedded runtime, a
515    /// reactor of its own — without a thread or a descriptor per stream.
516    ///
517    /// One per table: a second caller is refused rather than handed a
518    /// descriptor whose signals the first would drain.
519    #[cfg(any(
520        target_os = "linux",
521        target_os = "freebsd",
522        target_os = "macos"
523    ))]
524    pub fn readiness(&self) -> Result<Readiness> {
525        self.table.take_readiness()
526    }
527
528    /// The epoch every ticket minted from this table carries right now.
529    pub fn epoch(&self) -> u64 {
530        self.table.epoch()
531    }
532
533    /// Take a slot in this process's lane. The endpoint is side A; the
534    /// ticket names side B and is what the other end opens.
535    pub fn create(&self) -> Result<(Endpoint, Ticket)> {
536        let (index, generation) = self.table.allocate()?;
537        let id = StreamId::make(
538            self.table.kind(),
539            self.table.node(),
540            (index % self.table.geometry().lane_capacity) as u32,
541            generation,
542        );
543        let handle = Arc::new(Handle {
544            table: Arc::clone(&self.table),
545            index,
546            id,
547            side: Side::A,
548        });
549        Ok((
550            Endpoint::new(handle),
551            Ticket { id, side: Side::B, epoch: self.table.epoch() },
552        ))
553    }
554
555    /// Hold the side a ticket names. Each side can be held once per stream.
556    pub fn open(
557        &self,
558        ticket: Ticket,
559    ) -> Result<Endpoint> {
560        let index = self.locate(ticket.id)?;
561        if ticket.epoch != self.table.epoch() {
562            return Err(Error::Stale(ticket.id));
563        }
564        let slot = &self.table.slots()[index];
565        if !slot.is(ticket.id.generation()) {
566            return Err(Error::Stale(ticket.id));
567        }
568        let claimed = &slot.claimed[ticket.side.index()];
569        if claimed
570            .compare_exchange(
571                SIDE_FREE,
572                SIDE_CLAIMED,
573                Ordering::SeqCst,
574                Ordering::SeqCst,
575            )
576            .is_err()
577        {
578            return Err(Error::AlreadyClaimed(ticket));
579        }
580        // The slot may have ended between the check and the claim; give the
581        // claim back rather than hold a side of whatever comes next.
582        if !slot.is(ticket.id.generation()) {
583            let _ = claimed.compare_exchange(
584                SIDE_CLAIMED,
585                SIDE_FREE,
586                Ordering::SeqCst,
587                Ordering::SeqCst,
588            );
589            return Err(Error::Stale(ticket.id));
590        }
591        slot.incarnation[ticket.side.index()]
592            .store(self.table.incarnation(), Ordering::Release);
593        slot.node[ticket.side.index()]
594            .store(self.table.node(), Ordering::Release);
595        // Counted for blocking waiters, and no doorbell rung. A claim is not
596        // something a parked task can be waiting for: readiness is `head`,
597        // `tail` and the flags, and no predicate reads who holds a side. A
598        // ring here woke both processes' drivers on every open to find
599        // nothing ready — measured, it was most of what an exchange cost
600        // above a Unix socket.
601        for direction in &slot.directions {
602            self.table.notify(index, slot, direction, table::Ring::Nobody);
603        }
604        Ok(Endpoint::new(Arc::new(Handle {
605            table: Arc::clone(&self.table),
606            index,
607            id: ticket.id,
608            side: ticket.side,
609        })))
610    }
611
612    /// Clear a stream for another exchange, and let its far side be opened
613    /// again — by any process.
614    ///
615    /// A slot created and released per exchange pays for both every time: two
616    /// compare-and-swaps, a reset, and a ring nobody has touched since it was
617    /// last used, so cold in cache and absent from the TLB. A creator that
618    /// keeps its slots between exchanges calls this instead. Its own claim
619    /// stands and the generation does not move, so the ticket stays valid;
620    /// the directions go back to nothing, and the far side changes hands.
621    ///
622    /// **The far side has to have let go, and the table is what says so.** Its
623    /// halves raise their flags as they drop and its claim is released after
624    /// them, so a claim that reads released belongs to a process that can no
625    /// longer touch the slot. That is the whole handshake — no message, no
626    /// flag to race with — and it is why the slot belongs to its creator
627    /// rather than to a pair: once reopened, whichever process the next
628    /// exchange is addressed to opens it.
629    ///
630    /// - `Ok`: re-armed; the next ticket written for it can be opened.
631    /// - [`Error::WouldBlock`]: the far side still holds it. Not yet — try
632    ///   another slot.
633    /// - [`Error::Stale`]: let it go. The address no longer names this stream,
634    ///   the far side died, or it never opened this exchange at all — and a
635    ///   side nobody opened may still be opened late by whoever the last
636    ///   ticket went to, so it is not safe to hand to anyone else. Dropping the
637    ///   endpoint empties the slot, and a late open is then refused as stale.
638    ///
639    /// The far side's owner goes back to what [`Streams::create`] writes for
640    /// nobody before its claim opens. A death report reads the claim first and
641    /// the owner after it, so without this a report about the process that
642    /// just let go could land on the one that opens next.
643    ///
644    /// **The caller owns the ordering.** Whoever re-arms must do it *before*
645    /// telling the other side that a new exchange has begun, so that the
646    /// message announcing the exchange is what publishes the cleared state.
647    pub fn rearm(
648        &self,
649        id: StreamId,
650    ) -> Result<()> {
651        let index = self.locate(id)?;
652        let slot = &self.table.slots()[index];
653        if !slot.is(id.generation()) {
654            return Err(Error::Stale(id));
655        }
656        let far = Side::B.index();
657        match slot.claimed[far].load(Ordering::SeqCst) {
658            SIDE_RELEASED => {}
659            SIDE_CLAIMED => return Err(Error::WouldBlock),
660            _ => return Err(Error::Stale(id)),
661        }
662        for direction in &slot.directions {
663            direction.clear();
664        }
665        slot.node[far].store(u16::MAX, Ordering::Relaxed);
666        slot.incarnation[far].store(0, Ordering::Relaxed);
667        // Released is terminal for its holder and a death report only touches
668        // a claimed side, so nothing else moves this word; the exchange is
669        // what publishes everything written above it.
670        slot.claimed[far]
671            .compare_exchange(
672                SIDE_RELEASED,
673                SIDE_FREE,
674                Ordering::SeqCst,
675                Ordering::SeqCst,
676            )
677            .map_err(|_| Error::Stale(id))?;
678        Ok(())
679    }
680
681    /// Whether the address names a live stream right now.
682    pub fn is_live(
683        &self,
684        id: StreamId,
685    ) -> bool {
686        self.locate(id)
687            .map(|index| self.table.slots()[index].is(id.generation()))
688            .unwrap_or(false)
689    }
690
691    /// Tell `node`'s process that side B of this stream is its to take:
692    /// the slot is put in that node's offer bitmap and its doorbell rung.
693    /// Discovery only; the ticket may just as well travel by any other
694    /// channel. An offer is seen once, by that node, through
695    /// [`Streams::take_offer`] and its waiting forms.
696    pub fn offer(
697        &self,
698        ticket: Ticket,
699        to: NodeId,
700    ) -> Result<()> {
701        let index = self.locate(ticket.id)?;
702        if ticket.epoch != self.table.epoch()
703            || !self.table.slots()[index].is(ticket.id.generation())
704        {
705            return Err(Error::Stale(ticket.id));
706        }
707        self.table.offer(index, to.get())
708    }
709
710    /// The next stream offered to this node, if any. A stream that ended
711    /// between the offer and now is skipped.
712    pub fn take_offer(&self) -> Option<Ticket> {
713        while let Some(index) = self.table.take_offer() {
714            if let Some(ticket) = self.ticket_for(index) {
715                return Some(ticket);
716            }
717        }
718        None
719    }
720
721    /// The same wait, bounded: `None` is the timeout and nothing else.
722    /// A worker whose shutdown must be observed waits this way rather
723    /// than parking on the next request forever.
724    #[cfg(any(
725        target_os = "linux",
726        target_os = "freebsd",
727        target_os = "macos"
728    ))]
729    pub fn take_offer_timeout(
730        &self,
731        timeout: Duration,
732    ) -> Result<Option<Ticket>> {
733        let deadline = Instant::now() + timeout;
734        loop {
735            if let Some(ticket) = self.take_offer() {
736                return Ok(Some(ticket));
737            }
738            let left = deadline.saturating_duration_since(Instant::now());
739            if left.is_zero() {
740                return Ok(self.take_offer());
741            }
742            if !self.table.wait_offer_timeout(left)? {
743                return Ok(self.take_offer());
744            }
745        }
746    }
747
748    /// Park the thread until a stream is offered to this node.
749    pub fn blocking_take_offer(&self) -> Result<Ticket> {
750        loop {
751            if let Some(ticket) = self.take_offer() {
752                return Ok(ticket);
753            }
754            self.table.wait_offer()?;
755        }
756    }
757
758    /// Readiness for a task: the next offer, or the waker is registered
759    /// and `Pending` comes back.
760    pub fn poll_take_offer(
761        &self,
762        cx: &mut std::task::Context<'_>,
763    ) -> std::task::Poll<Result<Ticket>> {
764        if let Some(ticket) = self.take_offer() {
765            return std::task::Poll::Ready(Ok(ticket));
766        }
767        if let Err(error) = self.table.register_offer(cx.waker()) {
768            return std::task::Poll::Ready(Err(error));
769        }
770        match self.take_offer() {
771            Some(ticket) => std::task::Poll::Ready(Ok(ticket)),
772            None => std::task::Poll::Pending,
773        }
774    }
775
776    /// Every process generation that still holds a side of a stream here.
777    ///
778    /// The mirror of `Pool::owners`, and for the same caller: a supervisor
779    /// that has to decide what to report dead after losing its own record.
780    /// It says who is *in* the table, never who is alive.
781    pub fn owners(&self) -> Vec<(NodeId, Incarnation)> {
782        let mut seen: Vec<(NodeId, Incarnation)> = Vec::new();
783        for slot in self.table.slots() {
784            if slot.state.load(Ordering::Acquire) != SLOT_LIVE {
785                continue;
786            }
787            for side in 0..2 {
788                if slot.claimed[side].load(Ordering::Acquire) != SIDE_CLAIMED {
789                    continue;
790                }
791                let holder = (
792                    NodeId::new(slot.node[side].load(Ordering::Acquire)),
793                    Incarnation::new(
794                        slot.incarnation[side].load(Ordering::Acquire),
795                    ),
796                );
797                if !seen.contains(&holder) {
798                    seen.push(holder);
799                }
800            }
801        }
802        seen.sort_by_key(|(node, incarnation)| (node.get(), incarnation.get()));
803        seen
804    }
805
806    /// A confirmed death, reported by whoever supervises processes: every
807    /// side that incarnation of `node` held, in any lane, is finished. Its
808    /// unfinished writes become resets and its reads are gone, so a holder
809    /// of the other side wakes with an error; that holder keeps the slot
810    /// until it drops its handles, and only then is the slot reused. A
811    /// report for another incarnation of the same node touches nothing.
812    pub fn node_dead(
813        &self,
814        node: NodeId,
815        incarnation: Incarnation,
816    ) {
817        self.table.node_dead(node.get(), incarnation.get());
818    }
819
820    /// This process took over its node from an earlier life: every side that
821    /// any other incarnation of this node held, in any lane, is finished, as
822    /// [`Streams::node_dead`] finishes one. The node id is inherited and the
823    /// incarnation is not, so what the node left behind is told apart from
824    /// this process's own by the stamp alone — no report from a supervisor
825    /// is needed.
826    ///
827    /// Call it once, before this process creates or opens anything. It is
828    /// sound only where a node is handed on after its holder is gone; a
829    /// node given to a second process while the first still runs would
830    /// have that process's streams ended under it.
831    pub fn supersede(&self) {
832        self.table.supersede();
833    }
834
835    /// Clear the table during quiescent owner boot and start a new epoch.
836    /// Every handle and every ticket from before goes stale.
837    pub fn reset_all(&self) {
838        self.table.reset_all();
839    }
840
841    fn ticket_for(
842        &self,
843        index: usize,
844    ) -> Option<Ticket> {
845        let slot = &self.table.slots()[index];
846        let generation = slot.generation.load(Ordering::Acquire);
847        if !slot.is(generation) {
848            return None;
849        }
850        let lane_capacity = self.table.geometry().lane_capacity;
851        let id = StreamId::make(
852            self.table.kind(),
853            (index / lane_capacity) as u16,
854            (index % lane_capacity) as u32,
855            generation,
856        );
857        Some(Ticket { id, side: Side::B, epoch: self.table.epoch() })
858    }
859
860    /// Remove the SHM object. Existing mappings stay valid until their
861    /// processes release them.
862    #[cfg(unix)]
863    pub fn unlink(&self) -> Result<()> {
864        self.table.unlink()
865    }
866
867    fn locate(
868        &self,
869        id: StreamId,
870    ) -> Result<usize> {
871        let geometry = self.table.geometry();
872        if id.kind() != self.table.kind()
873            || usize::from(id.node()) >= geometry.fleet_capacity
874            || id.slot() as usize >= geometry.lane_capacity
875            || id.generation() == 0
876            || id.generation() > GENERATION_MASK
877        {
878            return Err(Error::Malformed(id.to_string()));
879        }
880        Ok(usize::from(id.node()) * geometry.lane_capacity + id.slot() as usize)
881    }
882}
883
884/// One held side. Dropped when both halves are gone; that releases the side.
885struct Handle {
886    table: Arc<Table>,
887    index: usize,
888    id: StreamId,
889    side: Side,
890}
891
892impl Handle {
893    /// The other side's doorbell when `ring`, nobody's otherwise. Everything a
894    /// handle changes is news to its peer and never to itself; see
895    /// [`table::Ring`].
896    fn peer_if(
897        &self,
898        ring: bool,
899    ) -> table::Ring {
900        if ring {
901            table::Ring::Side(1 - self.side.index())
902        } else {
903            table::Ring::Nobody
904        }
905    }
906
907    fn slot(&self) -> Result<&Slot> {
908        let slot = &self.table.slots()[self.index];
909        if slot.is(self.id.generation()) {
910            Ok(slot)
911        } else {
912            Err(Error::Stale(self.id))
913        }
914    }
915
916    fn direction(
917        slot: &Slot,
918        direction: usize,
919    ) -> &Direction {
920        &slot.directions[direction]
921    }
922
923    /// One attempt at the direction this side writes into.
924    fn try_write(
925        &self,
926        buf: &[u8],
927    ) -> Result<usize> {
928        let slot = self.slot()?;
929        let direction = Self::direction(slot, self.side.write_direction());
930        let flags = direction.flags();
931        if flags & FLAG_RESET != 0 {
932            return Err(Error::Reset);
933        }
934        if flags & FLAG_FIN != 0 {
935            return Err(Error::Closed);
936        }
937        if flags & FLAG_READER_GONE != 0 {
938            return Err(Error::PeerGone);
939        }
940        let buffer_bytes = self.table.geometry().buffer_bytes;
941        let head = direction.head.load(Ordering::Relaxed);
942        let tail = direction.tail.load(Ordering::Acquire);
943        let free = buffer_bytes - (head - tail) as usize;
944        if buf.is_empty() {
945            return Ok(0);
946        }
947        if free == 0 {
948            return Err(Error::WouldBlock);
949        }
950        let len = buf.len().min(free);
951        let base = self.table.buffer(self.index, self.side.write_direction());
952        let start = (head as usize) & (buffer_bytes - 1);
953        let first = len.min(buffer_bytes - start);
954        // SAFETY: only this side writes this direction, and `[head, head+len)`
955        // is free space the reader will not touch until `head` moves.
956        unsafe {
957            std::ptr::copy_nonoverlapping(buf.as_ptr(), base.add(start), first);
958            std::ptr::copy_nonoverlapping(
959                buf.as_ptr().add(first),
960                base,
961                len - first,
962            );
963        }
964        direction.head.store(head + len as u64, Ordering::SeqCst);
965        // A reader parks only on an empty ring: it looks, registers, looks
966        // again. Whether one could be parked is the tail *after* this
967        // commit, never the one read before the copy — the reader may have
968        // drained the ring and parked while we were copying into it, and
969        // that older tail would say "nobody to wake" about a task asleep on
970        // these very bytes. Each side orders its own store before the
971        // other's load, so at least one of the two sees the other: the
972        // reader sees the new head and does not park, or this sees the
973        // drained tail and rings.
974        std::sync::atomic::fence(Ordering::SeqCst);
975        let drained = direction.tail.load(Ordering::SeqCst) >= head;
976        self.table.notify(self.index, slot, direction, self.peer_if(drained));
977        Ok(len)
978    }
979
980    /// Publish `buf` as one indivisible write, or publish nothing.
981    fn try_write_exact(
982        &self,
983        buf: &[u8],
984    ) -> Result<()> {
985        let slot = self.slot()?;
986        let direction = Self::direction(slot, self.side.write_direction());
987        let flags = direction.flags();
988        if flags & FLAG_RESET != 0 {
989            return Err(Error::Reset);
990        }
991        if flags & FLAG_FIN != 0 {
992            return Err(Error::Closed);
993        }
994        if flags & FLAG_READER_GONE != 0 {
995            return Err(Error::PeerGone);
996        }
997        let buffer_bytes = self.table.geometry().buffer_bytes;
998        if buf.len() > buffer_bytes {
999            return Err(Error::Malformed(format!(
1000                "atomic stream write is {} bytes; ring capacity is {buffer_bytes}",
1001                buf.len()
1002            )));
1003        }
1004        if buf.is_empty() {
1005            return Ok(());
1006        }
1007        let head = direction.head.load(Ordering::Relaxed);
1008        let tail = direction.tail.load(Ordering::Acquire);
1009        if buffer_bytes - ((head - tail) as usize) < buf.len() {
1010            return Err(Error::WouldBlock);
1011        }
1012        let base = self.table.buffer(self.index, self.side.write_direction());
1013        let start = (head as usize) & (buffer_bytes - 1);
1014        let first = buf.len().min(buffer_bytes - start);
1015        // SAFETY: the complete frame fits in free ring space owned by this writer.
1016        unsafe {
1017            std::ptr::copy_nonoverlapping(buf.as_ptr(), base.add(start), first);
1018            std::ptr::copy_nonoverlapping(
1019                buf.as_ptr().add(first),
1020                base,
1021                buf.len() - first,
1022            );
1023        }
1024        direction.head.store(head + buf.len() as u64, Ordering::SeqCst);
1025        std::sync::atomic::fence(Ordering::SeqCst);
1026        let drained = direction.tail.load(Ordering::SeqCst) >= head;
1027        self.table.notify(self.index, slot, direction, self.peer_if(drained));
1028        Ok(())
1029    }
1030
1031    /// One attempt at the direction this side reads from. `Ok(0)` with a
1032    /// non-empty buffer is clean end of stream.
1033    fn try_read(
1034        &self,
1035        buf: &mut [u8],
1036    ) -> Result<usize> {
1037        let slot = self.slot()?;
1038        let direction = Self::direction(slot, self.side.read_direction());
1039        let flags = direction.flags();
1040        if flags & FLAG_RESET != 0 {
1041            return Err(Error::Reset);
1042        }
1043        let head = direction.head.load(Ordering::Acquire);
1044        let tail = direction.tail.load(Ordering::Relaxed);
1045        let available = (head - tail) as usize;
1046        if buf.is_empty() {
1047            return Ok(0);
1048        }
1049        if available == 0 {
1050            if flags & FLAG_FIN != 0 {
1051                return Ok(0);
1052            }
1053            return Err(Error::WouldBlock);
1054        }
1055        let len = buf.len().min(available);
1056        let base = self.table.buffer(self.index, self.side.read_direction());
1057        let buffer_bytes = self.table.geometry().buffer_bytes;
1058        let start = (tail as usize) & (buffer_bytes - 1);
1059        let first = len.min(buffer_bytes - start);
1060        // SAFETY: `[tail, head)` was published by the writer's `Release`
1061        // store of `head`, acquired above, and is not rewritten until `tail`
1062        // moves past it.
1063        unsafe {
1064            std::ptr::copy_nonoverlapping(
1065                base.add(start),
1066                buf.as_mut_ptr(),
1067                first,
1068            );
1069            std::ptr::copy_nonoverlapping(
1070                base,
1071                buf.as_mut_ptr().add(first),
1072                len - first,
1073            );
1074        }
1075        direction.tail.store(tail + len as u64, Ordering::SeqCst);
1076        // The mirror image, and the other half of that agreement: a writer
1077        // parks only on a full ring, so the head read after this consume is
1078        // what says whether one is parked on the space just freed.
1079        std::sync::atomic::fence(Ordering::SeqCst);
1080        let head_now = direction.head.load(Ordering::SeqCst);
1081        let was_full = head_now - tail >= buffer_bytes as u64;
1082        self.table.notify(self.index, slot, direction, self.peer_if(was_full));
1083        Ok(len)
1084    }
1085
1086    /// Consume exactly one complete frame, or consume nothing.
1087    fn try_read_exact(
1088        &self,
1089        buf: &mut [u8],
1090    ) -> Result<()> {
1091        let slot = self.slot()?;
1092        let direction = Self::direction(slot, self.side.read_direction());
1093        let flags = direction.flags();
1094        if flags & FLAG_RESET != 0 {
1095            return Err(Error::Reset);
1096        }
1097        let buffer_bytes = self.table.geometry().buffer_bytes;
1098        if buf.len() > buffer_bytes {
1099            return Err(Error::Malformed(format!(
1100                "atomic stream read is {} bytes; ring capacity is {buffer_bytes}",
1101                buf.len()
1102            )));
1103        }
1104        if buf.is_empty() {
1105            return Ok(());
1106        }
1107        let head = direction.head.load(Ordering::Acquire);
1108        let tail = direction.tail.load(Ordering::Relaxed);
1109        let available = (head - tail) as usize;
1110        if available < buf.len() {
1111            if flags & FLAG_FIN != 0 {
1112                return Err(Error::Malformed(format!(
1113                    "stream ended with {available} bytes of a {} byte frame",
1114                    buf.len()
1115                )));
1116            }
1117            return Err(Error::WouldBlock);
1118        }
1119        let base = self.table.buffer(self.index, self.side.read_direction());
1120        let start = (tail as usize) & (buffer_bytes - 1);
1121        let first = buf.len().min(buffer_bytes - start);
1122        // SAFETY: the complete frame is within the writer-published range.
1123        unsafe {
1124            std::ptr::copy_nonoverlapping(
1125                base.add(start),
1126                buf.as_mut_ptr(),
1127                first,
1128            );
1129            std::ptr::copy_nonoverlapping(
1130                base,
1131                buf.as_mut_ptr().add(first),
1132                buf.len() - first,
1133            );
1134        }
1135        direction.tail.store(tail + buf.len() as u64, Ordering::SeqCst);
1136        std::sync::atomic::fence(Ordering::SeqCst);
1137        let head_now = direction.head.load(Ordering::SeqCst);
1138        let was_full = head_now - tail >= buffer_bytes as u64;
1139        self.table.notify(self.index, slot, direction, self.peer_if(was_full));
1140        Ok(())
1141    }
1142
1143    fn set_flag(
1144        &self,
1145        direction_index: usize,
1146        flag: u8,
1147    ) {
1148        if let Ok(slot) = self.slot() {
1149            let direction = Self::direction(slot, direction_index);
1150            let before = direction.flags.fetch_or(flag, Ordering::SeqCst);
1151            if before & flag == 0 {
1152                let ring = flag != FLAG_READER_GONE
1153                    || writer_may_park(direction, before);
1154                self.table.notify(
1155                    self.index,
1156                    slot,
1157                    direction,
1158                    self.peer_if(ring),
1159                );
1160            }
1161        }
1162    }
1163
1164    /// Park until `ready` holds for the direction, or the stream ends.
1165    /// The same wait, bounded: `false` is the timeout and nothing else.
1166    #[cfg(any(
1167        target_os = "linux",
1168        target_os = "freebsd",
1169        target_os = "macos"
1170    ))]
1171    fn wait_until_timeout(
1172        &self,
1173        direction_index: usize,
1174        ready: impl Fn(&Direction) -> bool,
1175        timeout: Duration,
1176    ) -> Result<bool> {
1177        let deadline = Instant::now() + timeout;
1178        loop {
1179            let slot = self.slot()?;
1180            let direction = Self::direction(slot, direction_index);
1181            if ready(direction) {
1182                return Ok(true);
1183            }
1184            let left = deadline.saturating_duration_since(Instant::now());
1185            if left.is_zero() {
1186                return Ok(false);
1187            }
1188            direction.waiters.fetch_add(1, Ordering::SeqCst);
1189            let since = direction.changes.load(Ordering::SeqCst);
1190            let outcome = if ready(direction) {
1191                Ok(true)
1192            } else {
1193                wait_on_timeout(&direction.changes, since, left)
1194            };
1195            direction.waiters.fetch_sub(1, Ordering::SeqCst);
1196            if !outcome? {
1197                // The deadline passed; one last look, since readiness may
1198                // have landed between the wait giving up and this line.
1199                let slot = self.slot()?;
1200                return Ok(ready(Self::direction(slot, direction_index)));
1201            }
1202        }
1203    }
1204
1205    fn wait_until(
1206        &self,
1207        direction_index: usize,
1208        ready: impl Fn(&Direction) -> bool,
1209    ) -> Result<()> {
1210        loop {
1211            let slot = self.slot()?;
1212            let direction = Self::direction(slot, direction_index);
1213            if ready(direction) {
1214                return Ok(());
1215            }
1216            direction.waiters.fetch_add(1, Ordering::SeqCst);
1217            let since = direction.changes.load(Ordering::SeqCst);
1218            let outcome = if ready(direction) {
1219                Ok(())
1220            } else {
1221                wait_on(&direction.changes, since)
1222            };
1223            direction.waiters.fetch_sub(1, Ordering::SeqCst);
1224            outcome?;
1225        }
1226    }
1227
1228    /// Check, register, check again: a change between the first look and
1229    /// the registration is not missed.
1230    fn poll_ready(
1231        &self,
1232        interest: Interest,
1233        ready: impl Fn(&Direction) -> bool,
1234        cx: &mut std::task::Context<'_>,
1235    ) -> std::task::Poll<Result<()>> {
1236        let slot = match self.slot() {
1237            Ok(slot) => slot,
1238            Err(error) => return std::task::Poll::Ready(Err(error)),
1239        };
1240        let direction = Self::direction(slot, interest.direction);
1241        if ready(direction) {
1242            return std::task::Poll::Ready(Ok(()));
1243        }
1244        if let Err(error) =
1245            self.table.register(self.index, interest, cx.waker())
1246        {
1247            return std::task::Poll::Ready(Err(error));
1248        }
1249        // Pairs with the fence in `writer_may_park`: a flag set without a
1250        // doorbell is one this second look is guaranteed to see.
1251        std::sync::atomic::fence(Ordering::SeqCst);
1252        if ready(direction) {
1253            std::task::Poll::Ready(Ok(()))
1254        } else {
1255            std::task::Poll::Pending
1256        }
1257    }
1258}
1259
1260/// Whether a task could be parked writing into `direction` as its reader
1261/// goes — the only case in which the reader going has to ring a doorbell.
1262///
1263/// Not a writer that has finished: it will never write or wait again. And not
1264/// while nothing is queued: with the ring empty every write fits, so nobody is
1265/// waiting for room. The ordinary end of an exchange is both — the reader has
1266/// taken everything, and often the writer has already said so — and ringing
1267/// there woke two drivers per exchange for no one.
1268///
1269/// The fence pairs with the one in [`Handle::poll_ready`]: either this sees
1270/// bytes a writer queued before parking, or that writer's second look sees
1271/// the flag. The reader is gone, so `tail` no longer moves, and a ring seen
1272/// empty here fills only through writes that will see the flag first.
1273fn writer_may_park(
1274    direction: &Direction,
1275    before: u8,
1276) -> bool {
1277    if before & (FLAG_FIN | FLAG_RESET) != 0 {
1278        return false;
1279    }
1280    std::sync::atomic::fence(Ordering::SeqCst);
1281    direction.head.load(Ordering::SeqCst)
1282        != direction.tail.load(Ordering::SeqCst)
1283}
1284
1285fn readable(direction: &Direction) -> bool {
1286    direction.head.load(Ordering::Acquire)
1287        != direction.tail.load(Ordering::Relaxed)
1288        || direction.flags() & (FLAG_FIN | FLAG_RESET) != 0
1289}
1290
1291fn writable(
1292    direction: &Direction,
1293    buffer_bytes: usize,
1294) -> bool {
1295    let queued = (direction.head.load(Ordering::Relaxed)
1296        - direction.tail.load(Ordering::Acquire)) as usize;
1297    queued < buffer_bytes
1298        || direction.flags() & (FLAG_FIN | FLAG_RESET | FLAG_READER_GONE) != 0
1299}
1300
1301fn writable_exact(
1302    direction: &Direction,
1303    buffer_bytes: usize,
1304    needed: usize,
1305) -> bool {
1306    let queued = (direction.head.load(Ordering::Relaxed)
1307        - direction.tail.load(Ordering::Acquire)) as usize;
1308    buffer_bytes.saturating_sub(queued) >= needed
1309        || direction.flags() & (FLAG_FIN | FLAG_RESET | FLAG_READER_GONE) != 0
1310}
1311
1312impl Drop for Handle {
1313    /// Both halves are gone: give the side back. The slot empties once no
1314    /// side holds it any more, and every address to this generation goes
1315    /// stale.
1316    fn drop(&mut self) {
1317        let slot = &self.table.slots()[self.index];
1318        if !slot.is(self.id.generation()) {
1319            return;
1320        }
1321        // Only a side that is still ours is ours to release: a death
1322        // report may have finished it already.
1323        if slot.claimed[self.side.index()]
1324            .compare_exchange(
1325                SIDE_CLAIMED,
1326                SIDE_RELEASED,
1327                Ordering::SeqCst,
1328                Ordering::SeqCst,
1329            )
1330            .is_err()
1331        {
1332            return;
1333        }
1334        // Side A is claimed at creation, so a free B was simply never
1335        // taken; a released or dead side is gone. Only a claimed side keeps
1336        // the slot alive.
1337        let other = slot.claimed[1 - self.side.index()].load(Ordering::SeqCst);
1338        if other != SIDE_CLAIMED {
1339            let _ = slot.state.compare_exchange(
1340                SLOT_LIVE,
1341                SLOT_EMPTY,
1342                Ordering::SeqCst,
1343                Ordering::SeqCst,
1344            );
1345            for direction in &slot.directions {
1346                // A parked waiter with a handle to this generation wakes
1347                // and finds it stale.
1348                direction.changes.fetch_add(1, Ordering::SeqCst);
1349                if direction.waiters.load(Ordering::SeqCst) > 0 {
1350                    wake_on(&direction.changes);
1351                }
1352            }
1353            self.table.registry.wake(self.index);
1354        }
1355    }
1356}
1357
1358/// Both ends of one side: what [`Streams::create`] and [`Streams::open`]
1359/// return. Reads and writes go through it directly, or [`Endpoint::split`]
1360/// gives the halves to two tasks.
1361pub struct Endpoint {
1362    read: ReadHalf,
1363    write: WriteHalf,
1364}
1365
1366impl Endpoint {
1367    fn new(handle: Arc<Handle>) -> Self {
1368        Self {
1369            read: ReadHalf { handle: Arc::clone(&handle) },
1370            write: WriteHalf { handle, shut: false },
1371        }
1372    }
1373
1374    pub fn id(&self) -> StreamId {
1375        self.read.handle.id
1376    }
1377
1378    pub fn side(&self) -> Side {
1379        self.read.handle.side
1380    }
1381
1382    pub fn split(self) -> (ReadHalf, WriteHalf) {
1383        (self.read, self.write)
1384    }
1385
1386    /// Put a split endpoint back together.
1387    ///
1388    /// For a caller that keeps its streams between exchanges: the halves go
1389    /// out to whoever pumps the bodies and come back here, so the slot is
1390    /// never given up and never claimed again. Neither half is dropped on the
1391    /// way through, so neither terminal flag is set.
1392    ///
1393    /// Refused if the two halves are not the same side of the same stream,
1394    /// which would otherwise build an endpoint that reads one slot and writes
1395    /// another.
1396    pub fn rejoin(
1397        read: ReadHalf,
1398        write: WriteHalf,
1399    ) -> Result<Self> {
1400        if read.id() != write.id() {
1401            return Err(Error::Stale(read.id()));
1402        }
1403        Ok(Self { read, write })
1404    }
1405
1406    /// Clear this creator-held endpoint for another peer and restore its
1407    /// process-local write state.
1408    ///
1409    /// [`Streams::rearm`] clears the shared directions after side B has let
1410    /// go. An endpoint that sent FIN also remembers that fact locally in its
1411    /// write half, so reusing the slot through the endpoint needs to clear
1412    /// both layers. Keeping that pairing here prevents a caller from
1413    /// successfully rearming SHM and then finding its retained writer still
1414    /// closed.
1415    ///
1416    /// Only side A, the creator-held side, is reusable. The ticket remains
1417    /// valid because rearming does not change the stream generation.
1418    pub fn rearm(
1419        &mut self,
1420        streams: &Streams,
1421    ) -> Result<()> {
1422        if self.side() != Side::A {
1423            return Err(Error::Malformed(
1424                "only the creator side of a stream can be rearmed".to_owned(),
1425            ));
1426        }
1427        streams.rearm(self.id())?;
1428        self.write.shut = false;
1429        Ok(())
1430    }
1431
1432    pub fn try_read(
1433        &self,
1434        buf: &mut [u8],
1435    ) -> Result<usize> {
1436        self.read.try_read(buf)
1437    }
1438
1439    pub fn try_read_exact(
1440        &self,
1441        buf: &mut [u8],
1442    ) -> Result<()> {
1443        self.read.try_read_exact(buf)
1444    }
1445
1446    pub fn blocking_read(
1447        &self,
1448        buf: &mut [u8],
1449    ) -> Result<usize> {
1450        self.read.blocking_read(buf)
1451    }
1452
1453    pub fn blocking_read_chunk(
1454        &self,
1455        max: usize,
1456    ) -> Result<Bytes> {
1457        self.read.blocking_read_chunk(max)
1458    }
1459
1460    pub fn try_write(
1461        &self,
1462        buf: &[u8],
1463    ) -> Result<usize> {
1464        self.write.try_write(buf)
1465    }
1466
1467    pub fn try_write_exact(
1468        &self,
1469        buf: &[u8],
1470    ) -> Result<()> {
1471        self.write.try_write_exact(buf)
1472    }
1473
1474    pub fn blocking_write(
1475        &self,
1476        buf: &[u8],
1477    ) -> Result<usize> {
1478        self.write.blocking_write(buf)
1479    }
1480
1481    pub fn blocking_write_all(
1482        &self,
1483        buf: &[u8],
1484    ) -> Result<()> {
1485        self.write.blocking_write_all(buf)
1486    }
1487
1488    pub fn finish(&mut self) -> Result<()> {
1489        self.write.finish()
1490    }
1491
1492    pub fn reset(&mut self) {
1493        self.write.reset()
1494    }
1495}
1496
1497/// The reading end of one side. Dropping it tells the peer's writer that
1498/// nothing more will be read.
1499pub struct ReadHalf {
1500    handle: Arc<Handle>,
1501}
1502
1503impl ReadHalf {
1504    pub fn id(&self) -> StreamId {
1505        self.handle.id
1506    }
1507
1508    /// Read what is there now. `Ok(0)` is clean end of stream;
1509    /// [`Error::WouldBlock`] means nothing yet.
1510    pub fn try_read(
1511        &self,
1512        buf: &mut [u8],
1513    ) -> Result<usize> {
1514        self.handle.try_read(buf)
1515    }
1516
1517    /// Read the complete buffer atomically. When it is not all available,
1518    /// returns [`Error::WouldBlock`] without consuming a prefix.
1519    pub fn try_read_exact(
1520        &self,
1521        buf: &mut [u8],
1522    ) -> Result<()> {
1523        self.handle.try_read_exact(buf)
1524    }
1525
1526    /// Park until something can be read, then read it. `Ok(0)` is clean
1527    /// end of stream. Blocks the thread; an async runtime uses `AsyncRead`.
1528    pub fn blocking_read(
1529        &self,
1530        buf: &mut [u8],
1531    ) -> Result<usize> {
1532        loop {
1533            match self.handle.try_read(buf) {
1534                Err(Error::WouldBlock) => self.wait_readable()?,
1535                other => return other,
1536            }
1537        }
1538    }
1539
1540    /// Tell the peer's writer that nothing more will be read, without letting
1541    /// go of the stream.
1542    ///
1543    /// What dropping this half says, for a holder that keeps its halves
1544    /// between exchanges. Without it a writer the reader has given up on —
1545    /// a client gone mid-response — waits on a full ring that nobody will
1546    /// empty. Re-arming the stream clears it again.
1547    pub fn stop_reading(&self) {
1548        self.handle
1549            .set_flag(self.handle.side.read_direction(), FLAG_READER_GONE);
1550    }
1551
1552    /// Up to `max` bytes as one owned chunk; empty at clean end of stream.
1553    pub fn blocking_read_chunk(
1554        &self,
1555        max: usize,
1556    ) -> Result<Bytes> {
1557        let mut chunk = BytesMut::zeroed(max);
1558        let len = self.blocking_read(&mut chunk)?;
1559        chunk.truncate(len);
1560        Ok(chunk.freeze())
1561    }
1562
1563    /// The same wait, bounded: `false` is the timeout and nothing else.
1564    #[cfg(any(
1565        target_os = "linux",
1566        target_os = "freebsd",
1567        target_os = "macos"
1568    ))]
1569    pub fn wait_readable_timeout(
1570        &self,
1571        timeout: Duration,
1572    ) -> Result<bool> {
1573        self.handle.wait_until_timeout(
1574            self.handle.side.read_direction(),
1575            readable,
1576            timeout,
1577        )
1578    }
1579
1580    /// Park until a read would make progress or the direction ended.
1581    pub fn wait_readable(&self) -> Result<()> {
1582        self.handle.wait_until(self.handle.side.read_direction(), readable)
1583    }
1584
1585    /// Readiness for a task: `Ready` when a read would make progress or the
1586    /// direction ended, otherwise the waker is registered and `Pending`
1587    /// comes back. Runtime-neutral; the `tokio` feature builds `AsyncRead`
1588    /// on it.
1589    pub fn poll_readable(
1590        &self,
1591        cx: &mut std::task::Context<'_>,
1592    ) -> std::task::Poll<Result<()>> {
1593        self.handle.poll_ready(
1594            Interest {
1595                direction: self.handle.side.read_direction(),
1596                writer: false,
1597            },
1598            readable,
1599            cx,
1600        )
1601    }
1602}
1603
1604impl Drop for ReadHalf {
1605    fn drop(&mut self) {
1606        self.handle
1607            .set_flag(self.handle.side.read_direction(), FLAG_READER_GONE);
1608    }
1609}
1610
1611/// The writing end of one side. Dropping it without [`WriteHalf::finish`]
1612/// resets the direction, as a dropped socket would.
1613pub struct WriteHalf {
1614    handle: Arc<Handle>,
1615    shut: bool,
1616}
1617
1618impl WriteHalf {
1619    /// Wait for the peer's confirmed node death, even while no bytes are
1620    /// being produced. Ordinary FIN and endpoint release are not death.
1621    ///
1622    /// Shares the writer's readiness registration: poll this from the same
1623    /// task as writes, not from a second concurrent writer task.
1624    pub fn poll_peer_dead(
1625        &self,
1626        cx: &mut std::task::Context<'_>,
1627    ) -> std::task::Poll<Result<()>> {
1628        let slot = match self.handle.slot() {
1629            Ok(slot) => slot,
1630            Err(error) => return std::task::Poll::Ready(Err(error)),
1631        };
1632        let peer = &slot.claimed[1 - self.handle.side.index()];
1633        if peer.load(Ordering::SeqCst) == SIDE_DEAD {
1634            return std::task::Poll::Ready(Ok(()));
1635        }
1636        let interest = Interest {
1637            direction: self.handle.side.write_direction(),
1638            writer: true,
1639        };
1640        if let Err(error) =
1641            self.handle.table.register(self.handle.index, interest, cx.waker())
1642        {
1643            return std::task::Poll::Ready(Err(error));
1644        }
1645        // node_dead always rings both directions. Check after registering so
1646        // a death racing the first check cannot strand this task.
1647        if peer.load(Ordering::SeqCst) == SIDE_DEAD {
1648            std::task::Poll::Ready(Ok(()))
1649        } else {
1650            std::task::Poll::Pending
1651        }
1652    }
1653
1654    pub fn id(&self) -> StreamId {
1655        self.handle.id
1656    }
1657
1658    /// Write what fits now; [`Error::WouldBlock`] when the ring is full.
1659    pub fn try_write(
1660        &self,
1661        buf: &[u8],
1662    ) -> Result<usize> {
1663        self.handle.try_write(buf)
1664    }
1665
1666    /// Write the complete buffer atomically. When it does not all fit,
1667    /// returns [`Error::WouldBlock`] without publishing a prefix.
1668    pub fn try_write_exact(
1669        &self,
1670        buf: &[u8],
1671    ) -> Result<()> {
1672        self.handle.try_write_exact(buf)
1673    }
1674
1675    /// Park until something fits, then write it. Blocks the thread.
1676    pub fn blocking_write(
1677        &self,
1678        buf: &[u8],
1679    ) -> Result<usize> {
1680        loop {
1681            match self.handle.try_write(buf) {
1682                Err(Error::WouldBlock) => self.wait_writable()?,
1683                other => return other,
1684            }
1685        }
1686    }
1687
1688    /// Write everything, waiting as needed. A failure part-way has already
1689    /// committed a prefix; nothing is replayed.
1690    pub fn blocking_write_all(
1691        &self,
1692        mut buf: &[u8],
1693    ) -> Result<()> {
1694        while !buf.is_empty() {
1695            let written = self.blocking_write(buf)?;
1696            buf = &buf[written..];
1697        }
1698        Ok(())
1699    }
1700
1701    /// No more bytes from this side (FIN); what is buffered still reaches
1702    /// the reader, then it sees a clean end. The other direction is
1703    /// unaffected. `AsyncWriteExt::shutdown` does the same.
1704    pub fn finish(&mut self) -> Result<()> {
1705        self.shut = true;
1706        self.handle.set_flag(self.handle.side.write_direction(), FLAG_FIN);
1707        Ok(())
1708    }
1709
1710    /// Abandon the direction: the reader gets an error and buffered bytes
1711    /// are discarded.
1712    pub fn reset(&mut self) {
1713        self.shut = true;
1714        self.handle.set_flag(self.handle.side.write_direction(), FLAG_RESET);
1715    }
1716
1717    /// The same wait, bounded: `false` is the timeout and nothing else,
1718    /// which is how a caller with a deadline of its own gives up without
1719    /// giving up its stream.
1720    #[cfg(any(
1721        target_os = "linux",
1722        target_os = "freebsd",
1723        target_os = "macos"
1724    ))]
1725    pub fn wait_writable_timeout(
1726        &self,
1727        timeout: Duration,
1728    ) -> Result<bool> {
1729        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1730        self.handle.wait_until_timeout(
1731            self.handle.side.write_direction(),
1732            |direction| writable(direction, buffer_bytes),
1733            timeout,
1734        )
1735    }
1736
1737    /// Park until a write would make progress or the direction ended.
1738    pub fn wait_writable(&self) -> Result<()> {
1739        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1740        self.handle
1741            .wait_until(self.handle.side.write_direction(), |direction| {
1742                writable(direction, buffer_bytes)
1743            })
1744    }
1745
1746    /// Park until an atomic write of `needed` bytes can be attempted.
1747    pub fn wait_writable_exact(
1748        &self,
1749        needed: usize,
1750    ) -> Result<()> {
1751        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1752        if needed > buffer_bytes {
1753            return Err(Error::Malformed(format!(
1754                "atomic stream write is {needed} bytes; ring capacity is {buffer_bytes}"
1755            )));
1756        }
1757        self.handle
1758            .wait_until(self.handle.side.write_direction(), |direction| {
1759                writable_exact(direction, buffer_bytes, needed)
1760            })
1761    }
1762
1763    /// Readiness for a task: `Ready` when a write would make progress or
1764    /// the direction ended, otherwise the waker is registered and `Pending`
1765    /// comes back. Runtime-neutral; the `tokio` feature builds `AsyncWrite`
1766    /// on it.
1767    pub fn poll_writable(
1768        &self,
1769        cx: &mut std::task::Context<'_>,
1770    ) -> std::task::Poll<Result<()>> {
1771        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1772        self.handle.poll_ready(
1773            Interest {
1774                direction: self.handle.side.write_direction(),
1775                writer: true,
1776            },
1777            |direction| writable(direction, buffer_bytes),
1778            cx,
1779        )
1780    }
1781
1782    /// Task readiness for an indivisible write of `needed` bytes.
1783    pub fn poll_writable_exact(
1784        &self,
1785        needed: usize,
1786        cx: &mut std::task::Context<'_>,
1787    ) -> std::task::Poll<Result<()>> {
1788        let buffer_bytes = self.handle.table.geometry().buffer_bytes;
1789        if needed > buffer_bytes {
1790            return std::task::Poll::Ready(Err(Error::Malformed(format!(
1791                "atomic stream write is {needed} bytes; ring capacity is {buffer_bytes}"
1792            ))));
1793        }
1794        self.handle.poll_ready(
1795            Interest {
1796                direction: self.handle.side.write_direction(),
1797                writer: true,
1798            },
1799            |direction| writable_exact(direction, buffer_bytes, needed),
1800            cx,
1801        )
1802    }
1803}
1804
1805impl Drop for WriteHalf {
1806    fn drop(&mut self) {
1807        if !self.shut {
1808            self.handle
1809                .set_flag(self.handle.side.write_direction(), FLAG_RESET);
1810        }
1811    }
1812}
1813
1814/// Whether this build can park on a shared word. A table refuses to open
1815/// where it cannot: a sleep loop wearing the shape of a wait is worse
1816/// than a clear no, and nothing above here should have to ask again.
1817#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1818pub(crate) fn waits_supported() -> bool {
1819    orbit_core::sync::supported()
1820}
1821
1822#[cfg(not(any(
1823    target_os = "linux",
1824    target_os = "freebsd",
1825    target_os = "macos"
1826)))]
1827pub(crate) fn waits_supported() -> bool {
1828    false
1829}
1830
1831/// Park until `word` no longer holds `expected`, through the platform's
1832/// shared address wait. There is no polling fallback: a table refuses to
1833/// open where the platform cannot wait, so by here it can.
1834#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1835/// Park until the word moves or `timeout` passes. `false` is the
1836/// timeout and nothing else.
1837#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
1838pub(crate) fn wait_on_timeout(
1839    word: &AtomicU32,
1840    expected: u32,
1841    timeout: Duration,
1842) -> Result<bool> {
1843    orbit_core::sync::wait_word_timeout(word, expected, timeout)
1844        .map_err(Error::Io)
1845}
1846
1847pub(crate) fn wait_on(
1848    word: &AtomicU32,
1849    expected: u32,
1850) -> Result<()> {
1851    orbit_core::sync::wait_word(word, expected).map_err(Error::Io)
1852}
1853
1854#[cfg(not(any(
1855    target_os = "linux",
1856    target_os = "freebsd",
1857    target_os = "macos"
1858)))]
1859pub(crate) fn wait_on(
1860    _word: &AtomicU32,
1861    _expected: u32,
1862) -> Result<()> {
1863    Err(Error::Io(std::io::Error::new(
1864        std::io::ErrorKind::Unsupported,
1865        "orbit-stream needs a platform that can wait on a shared word",
1866    )))
1867}
1868
1869/// Wake everyone parked on `word`; nothing to do where nobody can park.
1870pub(crate) fn wake_on(word: &AtomicU32) {
1871    #[cfg(any(
1872        target_os = "linux",
1873        target_os = "freebsd",
1874        target_os = "macos"
1875    ))]
1876    let _ = orbit_core::sync::wake_word(word);
1877    #[cfg(not(any(
1878        target_os = "linux",
1879        target_os = "freebsd",
1880        target_os = "macos"
1881    )))]
1882    let _ = word;
1883}
1884
1885pub(crate) fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
1886    mutex.lock().unwrap_or_else(|error| error.into_inner())
1887}
1888
1889#[cfg(test)]
1890mod tests {
1891    use std::sync::Arc;
1892    use std::time::Duration;
1893
1894    use orbit_core::{Fleet, NodeId};
1895
1896    use super::{
1897        Error, Incarnation, STREAM_BUFFER_BYTES, STREAM_LANE_CAPACITY, Side,
1898        Streams, Ticket,
1899    };
1900
1901    fn streams(name: &'static str) -> Streams {
1902        Streams::new(
1903            Arc::new(Fleet::join(name, 2).unwrap()),
1904            Incarnation::new(1),
1905        )
1906        .unwrap()
1907    }
1908
1909    #[test]
1910    fn bytes_cross_in_order_and_end_cleanly() {
1911        let streams = streams("stream-order");
1912        let (mut a, ticket) = streams.create().unwrap();
1913        assert_eq!(ticket.side, Side::B);
1914        assert_eq!(ticket.id.node(), 0);
1915        assert_eq!(ticket.epoch, streams.epoch());
1916        let b = streams.open(ticket).unwrap();
1917
1918        a.blocking_write_all(b"hello, ").unwrap();
1919        a.blocking_write_all(b"world").unwrap();
1920        a.finish().unwrap();
1921
1922        let mut out = Vec::new();
1923        let mut buf = [0_u8; 4];
1924        loop {
1925            let n = b.blocking_read(&mut buf).unwrap();
1926            if n == 0 {
1927                break;
1928            }
1929            out.extend_from_slice(&buf[..n]);
1930        }
1931        assert_eq!(out, b"hello, world");
1932        // The other direction is its own thing.
1933        b.blocking_write_all(b"back").unwrap();
1934        assert_eq!(a.blocking_read_chunk(16).unwrap().as_ref(), b"back");
1935    }
1936
1937    #[test]
1938    fn a_ticket_prints_and_parses() {
1939        let streams = streams("stream-ticket");
1940        let (_a, ticket) = streams.create().unwrap();
1941        let text = ticket.to_string();
1942        assert!(text.contains("/b/"), "{text}");
1943        assert_eq!(text.parse::<Ticket>().unwrap(), ticket);
1944        assert!(matches!("nope".parse::<Ticket>(), Err(Error::Malformed(_))));
1945        assert!(matches!("x/b".parse::<Ticket>(), Err(Error::Malformed(_))));
1946    }
1947
1948    #[test]
1949    fn a_side_is_held_once_and_a_released_stream_goes_stale() {
1950        let streams = streams("stream-claim");
1951        let (a, ticket) = streams.create().unwrap();
1952        let b = streams.open(ticket).unwrap();
1953        assert!(matches!(streams.open(ticket), Err(Error::AlreadyClaimed(_))));
1954        assert!(streams.is_live(ticket.id));
1955
1956        drop(a);
1957        // A's write half went without finishing: B reads a reset.
1958        let mut buf = [0_u8; 1];
1959        assert!(matches!(b.blocking_read(&mut buf), Err(Error::Reset)));
1960        drop(b);
1961        assert!(!streams.is_live(ticket.id));
1962        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
1963
1964        // Once the lane wraps, the slot is reused under a new generation and
1965        // the old ticket stays stale.
1966        for _ in 0..STREAM_LANE_CAPACITY - 1 {
1967            drop(streams.create().unwrap());
1968        }
1969        let (_a2, ticket2) = streams.create().unwrap();
1970        assert_eq!(ticket2.id.slot(), ticket.id.slot());
1971        assert_ne!(ticket2.id.generation(), ticket.id.generation());
1972        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
1973    }
1974
1975    #[test]
1976    fn a_reset_starts_a_new_epoch_and_refuses_old_tickets() {
1977        let streams = streams("stream-epoch");
1978        let (_a, ticket) = streams.create().unwrap();
1979        let before = streams.epoch();
1980        streams.reset_all();
1981        assert!(streams.epoch() > before);
1982        // The slot is free again and reinstalled from generation one, which
1983        // is exactly what the old ticket names; the epoch tells them apart.
1984        let (_a2, ticket2) = streams.create().unwrap();
1985        assert_eq!(ticket2.id, ticket.id);
1986        assert_ne!(ticket2.epoch, ticket.epoch);
1987        assert!(matches!(streams.open(ticket), Err(Error::Stale(_))));
1988        assert!(streams.open(ticket2).is_ok());
1989    }
1990
1991    #[test]
1992    fn a_slot_whose_generation_runs_out_is_not_reused() {
1993        // GENERATION_LIMIT is 4 under test: each slot can be installed four
1994        // times in one epoch, then it sits out until the next epoch.
1995        let streams = streams("stream-exhaust");
1996        let limit = super::layout::GENERATION_LIMIT as usize;
1997        for _ in 0..STREAM_LANE_CAPACITY * limit {
1998            drop(streams.create().unwrap());
1999        }
2000        assert!(matches!(streams.create(), Err(Error::Full { .. })));
2001        streams.reset_all();
2002        let (_a, ticket) = streams.create().unwrap();
2003        assert_eq!(ticket.id.generation(), 1);
2004    }
2005
2006    #[test]
2007    fn a_full_ring_blocks_the_writer_and_wraps_correctly() {
2008        let streams = streams("stream-wrap");
2009        let (a, ticket) = streams.create().unwrap();
2010        let b = streams.open(ticket).unwrap();
2011
2012        let filler = vec![7_u8; STREAM_BUFFER_BYTES];
2013        assert_eq!(a.try_write(&filler).unwrap(), STREAM_BUFFER_BYTES);
2014        assert!(matches!(a.try_write(b"x"), Err(Error::WouldBlock)));
2015
2016        let mut buf = vec![0_u8; STREAM_BUFFER_BYTES - 5];
2017        assert_eq!(b.blocking_read(&mut buf).unwrap(), STREAM_BUFFER_BYTES - 5);
2018        // Room again; the next write wraps around the end of the ring.
2019        let tail =
2020            (0..STREAM_BUFFER_BYTES).map(|i| i as u8).collect::<Vec<_>>();
2021        assert_eq!(a.try_write(&tail).unwrap(), STREAM_BUFFER_BYTES - 5);
2022
2023        let mut rest = vec![0_u8; 5];
2024        assert_eq!(b.blocking_read(&mut rest).unwrap(), 5);
2025        assert_eq!(rest, vec![7_u8; 5]);
2026        let mut wrapped = vec![0_u8; STREAM_BUFFER_BYTES];
2027        let mut got = 0;
2028        while got < STREAM_BUFFER_BYTES - 5 {
2029            got += b.blocking_read(&mut wrapped[got..]).unwrap();
2030        }
2031        assert_eq!(&wrapped[..got], &tail[..got]);
2032    }
2033
2034    #[test]
2035    fn exact_frames_never_publish_or_consume_a_prefix() {
2036        let streams = streams("stream-exact-frame");
2037        let (a, ticket) = streams.create().unwrap();
2038        let b = streams.open(ticket).unwrap();
2039
2040        let filler = vec![7_u8; STREAM_BUFFER_BYTES - 32];
2041        a.try_write_exact(&filler).unwrap();
2042        assert!(matches!(
2043            a.try_write_exact(&[9_u8; 64]),
2044            Err(Error::WouldBlock)
2045        ));
2046        let mut consumed = vec![0_u8; filler.len()];
2047        b.try_read_exact(&mut consumed).unwrap();
2048        assert_eq!(consumed, filler);
2049        assert!(matches!(b.try_read(&mut [0_u8; 1]), Err(Error::WouldBlock)));
2050
2051        a.try_write_exact(&[3_u8; 32]).unwrap();
2052        assert!(matches!(
2053            b.try_read_exact(&mut [0_u8; 64]),
2054            Err(Error::WouldBlock)
2055        ));
2056        let mut frame = [0_u8; 32];
2057        b.try_read_exact(&mut frame).unwrap();
2058        assert_eq!(frame, [3_u8; 32]);
2059    }
2060
2061    #[test]
2062    fn a_blocked_reader_is_woken_by_a_write_in_another_thread() {
2063        let streams = streams("stream-wake");
2064        let (a, ticket) = streams.create().unwrap();
2065        let b = streams.open(ticket).unwrap();
2066        let reader = std::thread::spawn(move || {
2067            let mut buf = [0_u8; 8];
2068            let n = b.blocking_read(&mut buf).unwrap();
2069            buf[..n].to_vec()
2070        });
2071        std::thread::sleep(Duration::from_millis(30));
2072        a.blocking_write_all(b"ping").unwrap();
2073        assert_eq!(reader.join().unwrap(), b"ping");
2074    }
2075
2076    #[test]
2077    fn a_reader_gone_fails_the_writer_and_a_lane_fills_up() {
2078        let streams = streams("stream-gone");
2079        let (a, ticket) = streams.create().unwrap();
2080        let b = streams.open(ticket).unwrap();
2081        let (b_read, _b_write) = b.split();
2082        drop(b_read);
2083        assert!(matches!(a.blocking_write(b"nobody"), Err(Error::PeerGone)));
2084
2085        let mut held = vec![a];
2086        for _ in 1..STREAM_LANE_CAPACITY {
2087            held.push(streams.create().unwrap().0);
2088        }
2089        assert!(matches!(streams.create(), Err(Error::Full { .. })));
2090    }
2091
2092    #[test]
2093    fn a_death_report_ends_that_incarnations_sides_only() {
2094        let streams = streams("stream-dead");
2095        let (a, ticket) = streams.create().unwrap();
2096        let b = streams.open(ticket).unwrap();
2097        b.blocking_write_all(b"partial").unwrap();
2098
2099        // Another incarnation of the same node: nothing happens.
2100        streams.node_dead(NodeId::ZERO, Incarnation::new(2));
2101        assert_eq!(a.blocking_read_chunk(16).unwrap().as_ref(), b"partial");
2102
2103        // Our incarnation died (as a supervisor would report about a peer):
2104        // both sides here are stamped with it, so both end and the slot
2105        // empties at once. A parked reader wakes with an error either way;
2106        // the two-incarnation case, where the survivor keeps the slot and
2107        // reads a reset, is the shared-memory test.
2108        let reader = std::thread::spawn(move || {
2109            let mut buf = [0_u8; 8];
2110            a.blocking_read(&mut buf).map(|_| ())
2111        });
2112        std::thread::sleep(Duration::from_millis(30));
2113        streams.node_dead(NodeId::ZERO, Incarnation::new(1));
2114        assert!(matches!(
2115            reader.join().unwrap(),
2116            Err(Error::Reset | Error::Stale(_))
2117        ));
2118        assert!(matches!(
2119            b.try_write(b"x"),
2120            Err(Error::Reset | Error::Stale(_))
2121        ));
2122        assert!(matches!(
2123            streams.open(ticket),
2124            Err(Error::AlreadyClaimed(_) | Error::Stale(_))
2125        ));
2126        drop(b);
2127        assert!(!streams.is_live(ticket.id));
2128    }
2129
2130    #[test]
2131    fn an_offer_is_taken_once_and_a_stale_offer_is_skipped() {
2132        let streams = streams("stream-offer");
2133        assert!(streams.take_offer().is_none());
2134        let (a, ticket) = streams.create().unwrap();
2135        streams.offer(ticket, NodeId::ZERO).unwrap();
2136        assert_eq!(streams.take_offer(), Some(ticket));
2137        assert!(streams.take_offer().is_none());
2138
2139        let (gone, stale) = streams.create().unwrap();
2140        streams.offer(stale, NodeId::ZERO).unwrap();
2141        drop(gone);
2142        assert!(streams.take_offer().is_none());
2143
2144        let taker = streams.clone();
2145        let waiter = std::thread::spawn(move || taker.blocking_take_offer());
2146        std::thread::sleep(Duration::from_millis(30));
2147        streams.offer(ticket, NodeId::ZERO).unwrap();
2148        assert_eq!(waiter.join().unwrap().unwrap(), ticket);
2149        drop(a);
2150    }
2151
2152    #[test]
2153    fn a_contested_claim_goes_to_exactly_one_opener() {
2154        let streams = streams("stream-race");
2155        for _ in 0..50 {
2156            let (_a, ticket) = streams.create().unwrap();
2157            let openers = (0..4)
2158                .map(|_| {
2159                    let streams = streams.clone();
2160                    std::thread::spawn(move || streams.open(ticket).is_ok())
2161                })
2162                .collect::<Vec<_>>();
2163            let won = openers
2164                .into_iter()
2165                .map(|opener| opener.join().unwrap())
2166                .filter(|won| *won)
2167                .count();
2168            assert_eq!(won, 1);
2169        }
2170    }
2171
2172    #[test]
2173    fn random_sizes_concatenate_exactly() {
2174        let streams = streams("stream-random");
2175        let (mut a, ticket) = streams.create().unwrap();
2176        let b = streams.open(ticket).unwrap();
2177        let payload = (0..300_000_u32)
2178            .map(|i| (i.wrapping_mul(2654435761) >> 13) as u8)
2179            .collect::<Vec<_>>();
2180        let expected = payload.clone();
2181        let writer = std::thread::spawn(move || {
2182            let mut rest = &payload[..];
2183            let mut step = 1_usize;
2184            while !rest.is_empty() {
2185                let len = step.min(rest.len());
2186                a.blocking_write_all(&rest[..len]).unwrap();
2187                rest = &rest[len..];
2188                step = (step * 7 + 13) % 9_001 + 1;
2189            }
2190            a.finish().unwrap();
2191        });
2192        let mut out = Vec::new();
2193        let mut step = 3_usize;
2194        loop {
2195            let chunk = b.blocking_read_chunk(step).unwrap();
2196            if chunk.is_empty() {
2197                break;
2198            }
2199            out.extend_from_slice(&chunk);
2200            step = (step * 5 + 11) % 4_099 + 1;
2201        }
2202        writer.join().unwrap();
2203        assert_eq!(out, expected);
2204    }
2205}