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