Skip to main content

orbit_stream/exchange/
pair.rs

1use std::fmt;
2use std::ops::{Deref, DerefMut};
3use std::str::FromStr;
4use std::sync::Arc;
5use std::time::Duration;
6
7use orbit_core::{Fleet, NodeId};
8
9use super::arena::Publication;
10use super::protocol::CONTROL_FRAME_BYTES;
11use super::{
12    ChunkDescriptor, ChunkGeometry, ChunkPlan, ControlEvent, ExchangeId, Flow, PayloadArena,
13    PayloadArenaSpec, PayloadChunk, ResetCode
14};
15use crate::{Error, Incarnation, ReadHalf, Result, Side, StreamSpec, Streams, Ticket, WriteHalf};
16
17/// How an exchange budgets payload memory for its two directions.
18///
19/// The shared form is the compact generic default. Directional arenas are for
20/// adapters whose two flows have deliberately different budgets (for example
21/// an upload and a streamed response) or must not consume each other's
22/// credit.
23#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24pub enum ExchangePayloadSpec {
25    Shared(PayloadArenaSpec),
26    Directional { a_to_b: PayloadArenaSpec, b_to_a: PayloadArenaSpec }
27}
28
29impl ExchangePayloadSpec {
30    /// Physical payload arena used by one byte-flow direction.
31    pub const fn for_flow(
32        self,
33        flow: Flow
34    ) -> PayloadArenaSpec {
35        match (self, flow) {
36            (Self::Shared(payload), _) => payload,
37            (Self::Directional { a_to_b, .. }, Flow::AtoB) => a_to_b,
38            (Self::Directional { b_to_a, .. }, Flow::BtoA) => b_to_a
39        }
40    }
41}
42
43/// The physical resources used by an exchange table.
44#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
45pub struct ExchangeSpec {
46    /// One lossless, bounded control table. Its two byte directions carry
47    /// fixed-size typed events, never application payload bytes.
48    pub control: StreamSpec,
49    /// Payload memory may be shared by both directions or split into two
50    /// independently bounded arenas.
51    pub payload: ExchangePayloadSpec
52}
53
54impl ExchangeSpec {
55    pub const fn new(
56        control: StreamSpec,
57        payload: PayloadArenaSpec
58    ) -> Self {
59        Self { control, payload: ExchangePayloadSpec::Shared(payload) }
60    }
61
62    pub const fn directional(
63        control: StreamSpec,
64        a_to_b: PayloadArenaSpec,
65        b_to_a: PayloadArenaSpec
66    ) -> Self {
67        Self { control, payload: ExchangePayloadSpec::Directional { a_to_b, b_to_a } }
68    }
69
70    /// Physical payload arena used by one byte-flow direction.
71    pub const fn payload_for(
72        self,
73        flow: Flow
74    ) -> PayloadArenaSpec {
75        self.payload.for_flow(flow)
76    }
77
78    /// Physical chunk geometry used by one byte-flow direction.
79    pub const fn chunk_geometry(
80        self,
81        flow: Flow
82    ) -> Option<ChunkGeometry> {
83        self.payload_for(flow).chunk_geometry()
84    }
85
86    fn validate(self) -> Result<()> {
87        if self.control.buffer_bytes < CONTROL_FRAME_BYTES {
88            return Err(Error::Malformed(format!(
89                "exchange control ring is {} bytes; one control frame is {CONTROL_FRAME_BYTES}",
90                self.control.buffer_bytes
91            )));
92        }
93        match self.payload {
94            ExchangePayloadSpec::Shared(payload) if self.control.kind == payload.kind => {
95                return Err(Error::Malformed(
96                    "exchange control and payload need distinct SHM kinds".to_owned()
97                ));
98            }
99            ExchangePayloadSpec::Directional { a_to_b, b_to_a }
100                if self.control.kind == a_to_b.kind
101                    || self.control.kind == b_to_a.kind
102                    || a_to_b.kind == b_to_a.kind =>
103            {
104                return Err(Error::Malformed(
105                    "exchange control and directional payloads need three distinct SHM kinds"
106                        .to_owned()
107                ));
108            }
109            _ => {}
110        }
111        Ok(())
112    }
113}
114
115/// The client representative's capability to open the other side of an
116/// exchange. It may travel through an Orbit offer or any setup channel.
117#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
118pub struct ExchangeTicket(Ticket);
119
120impl ExchangeTicket {
121    pub const fn from_stream_ticket(ticket: Ticket) -> Self {
122        Self(ticket)
123    }
124
125    pub const fn stream_ticket(self) -> Ticket {
126        self.0
127    }
128
129    pub const fn id(self) -> ExchangeId {
130        ExchangeId::from_stream_id(self.0.id)
131    }
132}
133
134impl fmt::Display for ExchangeTicket {
135    fn fmt(
136        &self,
137        f: &mut fmt::Formatter<'_>
138    ) -> fmt::Result {
139        self.0.fmt(f)
140    }
141}
142
143impl FromStr for ExchangeTicket {
144    type Err = Error;
145
146    fn from_str(text: &str) -> Result<Self> {
147        text.parse().map(Self)
148    }
149}
150
151#[derive(Clone)]
152enum PayloadArenas {
153    Shared(PayloadArena),
154    Directional { a_to_b: PayloadArena, b_to_a: PayloadArena }
155}
156
157impl PayloadArenas {
158    fn for_flow(
159        &self,
160        flow: Flow
161    ) -> PayloadArena {
162        match (self, flow) {
163            (Self::Shared(arena), _) => arena.clone(),
164            (Self::Directional { a_to_b, .. }, Flow::AtoB) => a_to_b.clone(),
165            (Self::Directional { b_to_a, .. }, Flow::BtoA) => b_to_a.clone()
166        }
167    }
168
169    fn node_dead(
170        &self,
171        node: NodeId,
172        incarnation: Incarnation
173    ) {
174        match self {
175            Self::Shared(arena) => arena.node_dead(node, incarnation),
176            Self::Directional { a_to_b, b_to_a } => {
177                a_to_b.node_dead(node, incarnation);
178                b_to_a.node_dead(node, incarnation);
179            }
180        }
181    }
182
183    fn reset_all(&self) {
184        match self {
185            Self::Shared(arena) => arena.reset_all(),
186            Self::Directional { a_to_b, b_to_a } => {
187                a_to_b.reset_all();
188                b_to_a.reset_all();
189            }
190        }
191    }
192
193    #[cfg(unix)]
194    fn unlink(&self) -> Result<()> {
195        match self {
196            Self::Shared(arena) => arena.unlink(),
197            Self::Directional { a_to_b, b_to_a } => {
198                a_to_b.unlink()?;
199                b_to_a.unlink()
200            }
201        }
202    }
203}
204
205/// One fleet's exchange transport: one duplex control table and either one
206/// shared payload arena or one independently bounded arena per direction.
207#[derive(Clone)]
208pub struct Exchanges {
209    control: Streams,
210    payload: PayloadArenas
211}
212
213impl Exchanges {
214    pub fn open(
215        fleet: Arc<Fleet>,
216        incarnation: Incarnation,
217        spec: ExchangeSpec
218    ) -> Result<Self> {
219        spec.validate()?;
220        let control = Streams::with_spec(Arc::clone(&fleet), incarnation, spec.control)?;
221        let payload = match spec.payload {
222            ExchangePayloadSpec::Shared(payload) => {
223                PayloadArenas::Shared(PayloadArena::open(fleet, incarnation, payload)?)
224            }
225            ExchangePayloadSpec::Directional { a_to_b, b_to_a } => PayloadArenas::Directional {
226                a_to_b: PayloadArena::open(Arc::clone(&fleet), incarnation, a_to_b)?,
227                b_to_a: PayloadArena::open(fleet, incarnation, b_to_a)?
228            }
229        };
230        Ok(Self { control, payload })
231    }
232
233    /// Create side A. Its sender writes A-to-B and its receiver reads B-to-A.
234    /// The ticket opens side B.
235    pub fn create(&self) -> Result<(ExchangeEndpoint, ExchangeTicket)> {
236        let (endpoint, ticket) = self.control.create()?;
237        Ok((self.endpoint(endpoint), ExchangeTicket(ticket)))
238    }
239
240    /// Whether side A can currently reserve its first flow payload.
241    /// Session transports use this before reserving a remote resource so
242    /// payload backpressure does not become an abandoned resource lease.
243    pub fn create_payload_available(
244        &self,
245        payload_len: usize
246    ) -> Result<bool> {
247        self.payload.for_flow(Flow::AtoB).is_available(payload_len)
248    }
249
250    /// Task readiness for side A's first flow payload credit.
251    pub fn poll_create_payload_available(
252        &self,
253        payload_len: usize,
254        cx: &mut std::task::Context<'_>
255    ) -> std::task::Poll<Result<()>> {
256        self.payload.for_flow(Flow::AtoB).poll_available(payload_len, cx)
257    }
258
259    /// Open side B. Its sender writes B-to-A and its receiver reads A-to-B.
260    /// Both directions have independent lifecycle and may start in either order.
261    pub fn open_peer(
262        &self,
263        ticket: ExchangeTicket
264    ) -> Result<ExchangeEndpoint> {
265        let endpoint = self.control.open(ticket.0)?;
266        Ok(self.endpoint(endpoint))
267    }
268
269    fn endpoint(
270        &self,
271        endpoint: crate::Endpoint
272    ) -> ExchangeEndpoint {
273        let id = ExchangeId::from_stream_id(endpoint.id());
274        let (send_flow, receive_flow) = match endpoint.side() {
275            Side::A => (Flow::AtoB, Flow::BtoA),
276            Side::B => (Flow::BtoA, Flow::AtoB)
277        };
278        let (read, write) = endpoint.split();
279        ExchangeEndpoint {
280            sender: Sender(Producer::new(id, send_flow, self.payload.for_flow(send_flow), write)),
281            receiver: Receiver(Consumer::new(
282                id,
283                receive_flow,
284                self.payload.for_flow(receive_flow),
285                read
286            ))
287        }
288    }
289
290    pub fn offer(
291        &self,
292        ticket: ExchangeTicket,
293        to: NodeId
294    ) -> Result<()> {
295        self.control.offer(ticket.0, to)
296    }
297
298    pub fn take_offer(&self) -> Option<ExchangeTicket> {
299        self.control.take_offer().map(ExchangeTicket)
300    }
301
302    /// Park until an exchange is offered to this node.
303    pub fn blocking_take_offer(&self) -> Result<ExchangeTicket> {
304        self.control.blocking_take_offer().map(ExchangeTicket)
305    }
306
307    /// Wait at most `timeout` for an offered exchange.
308    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
309    pub fn take_offer_timeout(
310        &self,
311        timeout: Duration
312    ) -> Result<Option<ExchangeTicket>> {
313        self.control.take_offer_timeout(timeout).map(|ticket| ticket.map(ExchangeTicket))
314    }
315
316    /// Task readiness for the next offered exchange.
317    pub fn poll_take_offer(
318        &self,
319        cx: &mut std::task::Context<'_>
320    ) -> std::task::Poll<Result<ExchangeTicket>> {
321        match self.control.poll_take_offer(cx) {
322            std::task::Poll::Ready(result) => std::task::Poll::Ready(result.map(ExchangeTicket)),
323            std::task::Poll::Pending => std::task::Poll::Pending
324        }
325    }
326
327    pub fn control(&self) -> &Streams {
328        &self.control
329    }
330
331    /// Apply a confirmed process-death report to control ownership and every
332    /// payload arena. This is never inferred from a replacement attaching.
333    pub fn node_dead(
334        &self,
335        node: NodeId,
336        incarnation: Incarnation
337    ) {
338        self.control.node_dead(node, incarnation);
339        self.payload.node_dead(node, incarnation);
340    }
341
342    /// Quiescent-owner maintenance only. Attaching a replacement process
343    /// never resets an exchange table or either payload arena implicitly.
344    pub fn reset_all(&self) {
345        self.control.reset_all();
346        self.payload.reset_all();
347    }
348
349    /// Remove the control and payload SHM names. Existing mappings remain
350    /// valid.
351    #[cfg(unix)]
352    pub fn unlink(&self) -> Result<()> {
353        self.control.unlink()?;
354        self.payload.unlink()
355    }
356}
357
358/// One held exchange side. A worker may hold endpoints on either side of many
359/// exchanges at once; sender and receiver are capabilities, not worker roles.
360pub struct ExchangeEndpoint {
361    sender: Sender,
362    receiver: Receiver
363}
364
365impl ExchangeEndpoint {
366    pub fn id(&self) -> ExchangeId {
367        self.sender.id()
368    }
369
370    pub fn sender(&mut self) -> &mut Sender {
371        &mut self.sender
372    }
373
374    pub fn receiver(&mut self) -> &mut Receiver {
375        &mut self.receiver
376    }
377
378    pub fn split(self) -> (Sender, Receiver) {
379        (self.sender, self.receiver)
380    }
381}
382
383#[derive(Clone, Copy, Debug, Eq, PartialEq)]
384enum ProducerState {
385    New,
386    Open,
387    Terminal
388}
389
390struct Producer {
391    exchange: ExchangeId,
392    flow: Flow,
393    arena: PayloadArena,
394    control: WriteHalf,
395    next_chunk: u64,
396    state: ProducerState
397}
398
399impl Producer {
400    fn new(
401        exchange: ExchangeId,
402        flow: Flow,
403        arena: PayloadArena,
404        control: WriteHalf
405    ) -> Self {
406        Self { exchange, flow, arena, control, next_chunk: 1, state: ProducerState::New }
407    }
408
409    fn id(&self) -> ExchangeId {
410        self.exchange
411    }
412
413    fn start(
414        &mut self,
415        metadata: Option<&[u8]>
416    ) -> Result<()> {
417        if self.state != ProducerState::New {
418            return Err(Error::Malformed("flow start was already sent".to_owned()));
419        }
420        if let Some(bytes) = metadata.filter(|bytes| !bytes.is_empty()) {
421            let mut pending = self.reserve_start(bytes.len())?;
422            pending.copy_from_slice(bytes);
423            pending.commit()?;
424        } else {
425            self.send(ControlEvent::Start {
426                exchange: self.exchange,
427                flow: self.flow,
428                metadata: None
429            })?;
430            self.state = ProducerState::Open;
431        }
432        Ok(())
433    }
434
435    fn data(
436        &mut self,
437        payload: &[u8]
438    ) -> Result<ChunkDescriptor> {
439        let mut pending = self.reserve_data(payload.len())?;
440        pending.copy_from_slice(payload);
441        pending.commit()
442    }
443
444    fn reserve_start(
445        &mut self,
446        payload_len: usize
447    ) -> Result<PendingStart<'_>> {
448        if self.state != ProducerState::New {
449            return Err(Error::Malformed("flow start was already sent".to_owned()));
450        }
451        let publication = self.arena.reserve(payload_len)?;
452        Ok(PendingStart { producer: self, publication })
453    }
454
455    fn reserve_data(
456        &mut self,
457        payload_len: usize
458    ) -> Result<PendingData<'_>> {
459        if self.state != ProducerState::Open {
460            return Err(Error::Malformed("flow data requires an open flow".to_owned()));
461        }
462        let publication = self.arena.reserve(payload_len)?;
463        Ok(PendingData { producer: self, publication })
464    }
465
466    fn commit_start(
467        &mut self,
468        publication: Publication
469    ) -> Result<ChunkDescriptor> {
470        let descriptor = self.descriptor(&publication);
471        publication.make_live();
472        self.send(ControlEvent::Start {
473            exchange: self.exchange,
474            flow: self.flow,
475            metadata: Some(descriptor)
476        })?;
477        publication.mark_published();
478        self.next_chunk += 1;
479        self.state = ProducerState::Open;
480        Ok(descriptor)
481    }
482
483    fn commit_data(
484        &mut self,
485        publication: Publication
486    ) -> Result<ChunkDescriptor> {
487        let descriptor = self.descriptor(&publication);
488        publication.make_live();
489        self.send(ControlEvent::Data(descriptor))?;
490        publication.mark_published();
491        self.next_chunk += 1;
492        Ok(descriptor)
493    }
494
495    fn finish(&mut self) -> Result<()> {
496        if self.state != ProducerState::Open {
497            return Err(Error::Malformed("flow FIN requires an open flow".to_owned()));
498        }
499        self.send(ControlEvent::Fin { exchange: self.exchange, flow: self.flow })?;
500        self.control.finish()?;
501        self.state = ProducerState::Terminal;
502        Ok(())
503    }
504
505    fn reset(
506        &mut self,
507        code: ResetCode
508    ) -> Result<()> {
509        if self.state == ProducerState::Terminal {
510            return Err(Error::Closed);
511        }
512        self.send(ControlEvent::Reset { exchange: self.exchange, flow: self.flow, code })?;
513        self.control.finish()?;
514        self.state = ProducerState::Terminal;
515        Ok(())
516    }
517
518    fn send(
519        &self,
520        event: ControlEvent
521    ) -> Result<()> {
522        self.control.try_write_exact(&event.encode())
523    }
524
525    fn descriptor(
526        &self,
527        publication: &Publication
528    ) -> ChunkDescriptor {
529        let (generation, first_slot, slot_count, payload_len, arena_kind, owner_node) =
530            publication.coordinates();
531        ChunkDescriptor::new(
532            self.exchange,
533            self.flow,
534            self.next_chunk,
535            generation,
536            first_slot,
537            slot_count,
538            payload_len,
539            arena_kind,
540            owner_node
541        )
542    }
543}
544
545/// A flow-start payload reserved directly in the shared SHM arena. The bytes
546/// become immutable and visible only when `commit` sends the matching start
547/// event. Dropping this value returns the reserved slots.
548pub struct PendingStart<'a> {
549    producer: &'a mut Producer,
550    publication: Publication
551}
552
553impl PendingStart<'_> {
554    pub fn as_mut_slice(&mut self) -> &mut [u8] {
555        self.publication.as_mut_slice()
556    }
557
558    pub fn commit(self) -> Result<ChunkDescriptor> {
559        self.producer.commit_start(self.publication)
560    }
561
562    /// Commit only the initialized prefix of a larger reservation. Any whole
563    /// trailing slots that prefix does not need return to the arena first.
564    pub fn commit_len(
565        mut self,
566        payload_len: usize
567    ) -> Result<ChunkDescriptor> {
568        self.publication.truncate(payload_len)?;
569        self.producer.commit_start(self.publication)
570    }
571}
572
573impl Deref for PendingStart<'_> {
574    type Target = [u8];
575
576    fn deref(&self) -> &Self::Target {
577        self.publication.as_slice()
578    }
579}
580
581impl DerefMut for PendingStart<'_> {
582    fn deref_mut(&mut self) -> &mut Self::Target {
583        self.as_mut_slice()
584    }
585}
586
587/// A data chunk reserved directly in the shared SHM arena. One commit publishes
588/// the complete variable-sized chunk and one descriptor.
589pub struct PendingData<'a> {
590    producer: &'a mut Producer,
591    publication: Publication
592}
593
594impl PendingData<'_> {
595    pub fn as_mut_slice(&mut self) -> &mut [u8] {
596        self.publication.as_mut_slice()
597    }
598
599    pub fn commit(self) -> Result<ChunkDescriptor> {
600        self.producer.commit_data(self.publication)
601    }
602
603    /// Commit only the initialized prefix of a larger reservation. This is
604    /// the direct-read path for producers that learn the byte count from IO.
605    pub fn commit_len(
606        mut self,
607        payload_len: usize
608    ) -> Result<ChunkDescriptor> {
609        self.publication.truncate(payload_len)?;
610        self.producer.commit_data(self.publication)
611    }
612}
613
614impl Deref for PendingData<'_> {
615    type Target = [u8];
616
617    fn deref(&self) -> &Self::Target {
618        self.publication.as_slice()
619    }
620}
621
622impl DerefMut for PendingData<'_> {
623    fn deref_mut(&mut self) -> &mut Self::Target {
624        self.as_mut_slice()
625    }
626}
627
628#[derive(Clone, Copy, Debug, Eq, PartialEq)]
629enum ConsumerState {
630    New,
631    Open,
632    Terminal
633}
634
635struct Consumer {
636    exchange: ExchangeId,
637    flow: Flow,
638    arena: PayloadArena,
639    control: ReadHalf,
640    next_chunk: u64,
641    state: ConsumerState
642}
643
644impl Consumer {
645    fn new(
646        exchange: ExchangeId,
647        flow: Flow,
648        arena: PayloadArena,
649        control: ReadHalf
650    ) -> Self {
651        Self { exchange, flow, arena, control, next_chunk: 1, state: ConsumerState::New }
652    }
653
654    fn id(&self) -> ExchangeId {
655        self.exchange
656    }
657
658    fn next(&mut self) -> Result<FlowEvent> {
659        if self.state == ConsumerState::Terminal {
660            return Err(Error::Closed);
661        }
662        let mut frame = [0_u8; CONTROL_FRAME_BYTES];
663        self.control.try_read_exact(&mut frame)?;
664        let event =
665            ControlEvent::decode(&frame).map_err(|reason| Error::Malformed(reason.to_owned()))?;
666        if event.exchange() != self.exchange || event.flow() != self.flow {
667            return Err(Error::Malformed(
668                "control event belongs to another exchange flow".to_owned()
669            ));
670        }
671        match event {
672            ControlEvent::Start { metadata, .. } if self.state == ConsumerState::New => {
673                let metadata =
674                    metadata.map(|descriptor| self.take_chunk(descriptor)).transpose()?;
675                self.state = ConsumerState::Open;
676                Ok(FlowEvent::Start { metadata })
677            }
678            ControlEvent::Data(descriptor) if self.state == ConsumerState::Open => {
679                let chunk = self.take_chunk(descriptor)?;
680                Ok(FlowEvent::Data(chunk))
681            }
682            ControlEvent::Fin { .. } if self.state == ConsumerState::Open => {
683                self.state = ConsumerState::Terminal;
684                Ok(FlowEvent::Fin)
685            }
686            ControlEvent::Reset { code, .. } => {
687                self.state = ConsumerState::Terminal;
688                Ok(FlowEvent::Reset(code))
689            }
690            _ => Err(Error::Malformed("control event violates the flow lifecycle".to_owned()))
691        }
692    }
693
694    fn take_chunk(
695        &mut self,
696        descriptor: ChunkDescriptor
697    ) -> Result<PayloadChunk> {
698        if descriptor.chunk_id() != self.next_chunk {
699            return Err(Error::Malformed(format!(
700                "flow expected chunk {}, received {}",
701                self.next_chunk,
702                descriptor.chunk_id()
703            )));
704        }
705        let chunk = self.arena.read(descriptor)?;
706        self.next_chunk += 1;
707        Ok(chunk)
708    }
709}
710
711/// The event program seen by a flow consumer.
712pub enum FlowEvent {
713    Start { metadata: Option<PayloadChunk> },
714    Data(PayloadChunk),
715    Fin,
716    Reset(ResetCode)
717}
718
719/// Typed callbacks for one flow. A handler decides what its events mean; the
720/// transport does not encode application sequencing policy.
721pub trait FlowHandler {
722    type Error;
723
724    fn on_start(
725        &mut self,
726        metadata: Option<PayloadChunk>
727    ) -> std::result::Result<(), Self::Error>;
728    fn on_data(
729        &mut self,
730        chunk: PayloadChunk
731    ) -> std::result::Result<(), Self::Error>;
732    fn on_fin(&mut self) -> std::result::Result<(), Self::Error>;
733    fn on_reset(
734        &mut self,
735        code: ResetCode
736    ) -> std::result::Result<(), Self::Error>;
737}
738
739#[derive(Debug)]
740pub enum DispatchError<E> {
741    Transport(Error),
742    Handler(E)
743}
744
745impl<E> From<Error> for DispatchError<E> {
746    fn from(value: Error) -> Self {
747        Self::Transport(value)
748    }
749}
750
751fn dispatch<H: FlowHandler>(
752    consumer: &mut Consumer,
753    handler: &mut H
754) -> std::result::Result<(), DispatchError<H::Error>> {
755    match consumer.next()? {
756        FlowEvent::Start { metadata } => handler.on_start(metadata),
757        FlowEvent::Data(chunk) => handler.on_data(chunk),
758        FlowEvent::Fin => handler.on_fin(),
759        FlowEvent::Reset(code) => handler.on_reset(code)
760    }
761    .map_err(DispatchError::Handler)
762}
763
764/// The capability to publish one endpoint's outbound flow.
765pub struct Sender(Producer);
766
767impl Sender {
768    pub fn id(&self) -> ExchangeId {
769        self.0.id()
770    }
771
772    pub fn flow(&self) -> Flow {
773        self.0.flow
774    }
775
776    /// Physical geometry of this sender's actual directional payload arena.
777    pub fn chunk_geometry(&self) -> ChunkGeometry {
778        self.0.arena.chunk_geometry()
779    }
780
781    /// Plan a known payload for this direction's actual arena geometry.
782    pub fn plan_chunks(
783        &self,
784        data_bytes: usize
785    ) -> Result<ChunkPlan> {
786        self.0.arena.plan_chunks(data_bytes)
787    }
788
789    /// Plan this direction with a runtime-selected slot count.
790    pub fn plan_chunks_with(
791        &self,
792        data_bytes: usize,
793        slots_per_chunk: usize
794    ) -> Result<ChunkPlan> {
795        self.0.arena.plan_chunks_with(data_bytes, slots_per_chunk)
796    }
797    pub fn start(
798        &mut self,
799        metadata: Option<&[u8]>
800    ) -> Result<()> {
801        self.0.start(metadata)
802    }
803
804    /// Reserve start metadata directly in the shared payload arena. No start
805    /// event is visible until the returned guard is committed.
806    pub fn reserve_start(
807        &mut self,
808        payload_len: usize
809    ) -> Result<PendingStart<'_>> {
810        self.0.reserve_start(payload_len)
811    }
812
813    pub fn data(
814        &mut self,
815        payload: &[u8]
816    ) -> Result<ChunkDescriptor> {
817        self.0.data(payload)
818    }
819
820    /// Reserve one variable-sized data chunk directly in the payload arena.
821    /// One commit emits one data descriptor.
822    pub fn reserve_data(
823        &mut self,
824        payload_len: usize
825    ) -> Result<PendingData<'_>> {
826        self.0.reserve_data(payload_len)
827    }
828
829    pub fn finish(&mut self) -> Result<()> {
830        self.0.finish()
831    }
832
833    pub fn reset(
834        &mut self,
835        code: ResetCode
836    ) -> Result<()> {
837        self.0.reset(code)
838    }
839
840    pub fn wait_writable(&self) -> Result<()> {
841        self.0.control.wait_writable_exact(CONTROL_FRAME_BYTES)
842    }
843
844    pub fn poll_writable(
845        &self,
846        cx: &mut std::task::Context<'_>
847    ) -> std::task::Poll<Result<()>> {
848        self.0.control.poll_writable_exact(CONTROL_FRAME_BYTES, cx)
849    }
850
851    /// Wait for both one control frame and the payload credit needed by the
852    /// next publication. Use zero for FIN, RESET, or a start without metadata.
853    pub fn wait_ready(
854        &self,
855        payload_len: usize
856    ) -> Result<()> {
857        self.0.control.wait_writable_exact(CONTROL_FRAME_BYTES)?;
858        if payload_len != 0 {
859            self.0.arena.wait_available(payload_len)?;
860        }
861        Ok(())
862    }
863
864    /// Task readiness for the same combined control and payload capacity.
865    /// Both sources register before `Pending` is returned.
866    pub fn poll_ready(
867        &self,
868        payload_len: usize,
869        cx: &mut std::task::Context<'_>
870    ) -> std::task::Poll<Result<()>> {
871        let control = self.0.control.poll_writable_exact(CONTROL_FRAME_BYTES, cx);
872        let payload = if payload_len == 0 {
873            std::task::Poll::Ready(Ok(()))
874        } else {
875            self.0.arena.poll_available(payload_len, cx)
876        };
877        match (control, payload) {
878            (std::task::Poll::Ready(Err(error)), _) | (_, std::task::Poll::Ready(Err(error))) => {
879                std::task::Poll::Ready(Err(error))
880            }
881            (std::task::Poll::Ready(Ok(())), std::task::Poll::Ready(Ok(()))) => {
882                std::task::Poll::Ready(Ok(()))
883            }
884            _ => std::task::Poll::Pending
885        }
886    }
887}
888
889/// The capability to consume the peer endpoint's outbound flow.
890pub struct Receiver(Consumer);
891
892impl Receiver {
893    pub fn id(&self) -> ExchangeId {
894        self.0.id()
895    }
896
897    pub fn flow(&self) -> Flow {
898        self.0.flow
899    }
900
901    pub fn try_next(&mut self) -> Result<FlowEvent> {
902        self.0.next()
903    }
904
905    pub fn dispatch<H: FlowHandler>(
906        &mut self,
907        handler: &mut H
908    ) -> std::result::Result<(), DispatchError<H::Error>> {
909        dispatch(&mut self.0, handler)
910    }
911
912    pub fn wait_readable(&self) -> Result<()> {
913        self.0.control.wait_readable()
914    }
915
916    pub fn poll_readable(
917        &self,
918        cx: &mut std::task::Context<'_>
919    ) -> std::task::Poll<Result<()>> {
920        self.0.control.poll_readable(cx)
921    }
922}
923
924#[cfg(test)]
925mod tests {
926    use super::*;
927
928    fn exchanges() -> Exchanges {
929        Exchanges::open(
930            Arc::new(Fleet::join("exchange-pair-test", 2).expect("fleet")),
931            Incarnation::new(1),
932            ExchangeSpec::new(StreamSpec::new(230, 8, 512), PayloadArenaSpec::new(231, 8, 64))
933        )
934        .expect("exchanges")
935    }
936
937    #[test]
938    fn peer_can_send_before_the_first_flow_finishes() {
939        let exchanges = exchanges();
940        let (server, ticket) = exchanges.create().expect("server");
941        let client = exchanges.open_peer(ticket).expect("client");
942        let (mut request_out, mut response_in) = server.split();
943        let (mut response_out, mut request_in) = client.split();
944
945        request_out.start(Some(b"request-head")).expect("request start");
946        let request_descriptor = request_out.data(&[7_u8; 192]).expect("request data");
947        let request_metadata = match request_in.try_next().expect("request start event") {
948            FlowEvent::Start { metadata: Some(metadata) } => metadata,
949            _ => panic!("expected request start")
950        };
951        assert_eq!(&*request_metadata, b"request-head");
952        let request_chunk = match request_in.try_next().expect("request data event") {
953            FlowEvent::Data(chunk) => chunk,
954            _ => panic!("expected request data")
955        };
956        assert_eq!(request_descriptor.arena_kind(), 231);
957        assert_eq!(request_descriptor.slot_count(), 3);
958
959        // Both directions share one arena, but their flow lifecycles are
960        // independent, so the peer can start before the first flow finishes.
961        response_out.start(Some(b"response-head")).expect("early response start");
962        let response_descriptor = response_out.data(b"response").expect("response data");
963        assert_eq!(response_descriptor.arena_kind(), 231);
964        assert!(matches!(response_in.try_next(), Ok(FlowEvent::Start { .. })));
965        let response_chunk = match response_in.try_next().expect("response data event") {
966            FlowEvent::Data(chunk) => chunk,
967            _ => panic!("expected response data")
968        };
969        assert_eq!(&*response_chunk, b"response");
970
971        drop(request_chunk);
972        request_out.finish().expect("request fin");
973        assert!(matches!(request_in.try_next(), Ok(FlowEvent::Fin)));
974        response_out.finish().expect("response fin");
975        assert!(matches!(response_in.try_next(), Ok(FlowEvent::Fin)));
976    }
977
978    #[test]
979    fn directional_payloads_keep_each_flows_credit_and_kind_separate() {
980        let exchanges = Exchanges::open(
981            Arc::new(Fleet::join("exchange-directional-test", 2).expect("fleet")),
982            Incarnation::new(1),
983            ExchangeSpec::directional(
984                StreamSpec::new(220, 8, 512),
985                PayloadArenaSpec::new(221, 1, 64),
986                PayloadArenaSpec::new(222, 1, 128)
987            )
988        )
989        .expect("directional exchanges");
990        let (mut side_a, ticket) = exchanges.create().expect("side A");
991        let mut side_b = exchanges.open_peer(ticket).expect("side B");
992
993        let request_geometry = side_a.sender().chunk_geometry();
994        let response_geometry = side_b.sender().chunk_geometry();
995        assert_eq!(request_geometry.slot_payload_bytes(), 64);
996        assert_eq!(response_geometry.slot_payload_bytes(), 128);
997        assert_eq!(request_geometry.chunk_bytes(1), Some(64));
998        assert_eq!(response_geometry.chunk_bytes(1), Some(128));
999
1000        side_a.sender().start(None).expect("A-to-B start");
1001        assert!(matches!(side_b.receiver().try_next(), Ok(FlowEvent::Start { .. })));
1002        side_b.sender().start(None).expect("B-to-A start");
1003        assert!(matches!(side_a.receiver().try_next(), Ok(FlowEvent::Start { .. })));
1004
1005        let a = side_a.sender().data(&[1; 64]).expect("A-to-B payload");
1006        let b = side_b.sender().data(&[2; 128]).expect("B-to-A payload");
1007        assert_eq!(a.arena_kind(), 221);
1008        assert_eq!(b.arena_kind(), 222);
1009
1010        let a_payload = match side_b.receiver().try_next().expect("A-to-B data") {
1011            FlowEvent::Data(chunk) => chunk,
1012            _ => panic!("expected A-to-B data")
1013        };
1014        let b_payload = match side_a.receiver().try_next().expect("B-to-A data") {
1015            FlowEvent::Data(chunk) => chunk,
1016            _ => panic!("expected B-to-A data")
1017        };
1018        assert_eq!(&*a_payload, &[1; 64]);
1019        assert_eq!(&*b_payload, &[2; 128]);
1020    }
1021
1022    #[test]
1023    fn a_reserved_chunk_is_one_descriptor_over_many_slots() {
1024        let exchanges = Exchanges::open(
1025            Arc::new(Fleet::join("exchange-reservation-test", 2).expect("fleet")),
1026            Incarnation::new(1),
1027            ExchangeSpec::new(StreamSpec::new(233, 8, 512), PayloadArenaSpec::new(234, 8, 256))
1028        )
1029        .expect("exchanges");
1030        let (mut server, ticket) = exchanges.create().expect("server");
1031        let mut client = exchanges.open_peer(ticket).expect("client");
1032
1033        let expected: Vec<u8> = (0..777).map(|index| (index % 251) as u8).collect();
1034        let descriptor = {
1035            let mut pending = server.sender().reserve_start(expected.len()).expect("reserve");
1036            pending.copy_from_slice(&expected);
1037            pending.commit().expect("commit")
1038        };
1039        assert_eq!(descriptor.payload_len(), 777);
1040        assert_eq!(descriptor.slot_count(), 4);
1041        let received = match client.receiver().try_next().expect("start") {
1042            FlowEvent::Start { metadata: Some(metadata) } => metadata,
1043            _ => panic!("expected start metadata")
1044        };
1045        assert_eq!(&*received, expected);
1046    }
1047
1048    #[test]
1049    fn dropping_an_uncommitted_chunk_returns_credit_without_an_event() {
1050        let exchanges = exchanges();
1051        let (mut server, ticket) = exchanges.create().expect("server");
1052        let mut client = exchanges.open_peer(ticket).expect("client");
1053        server.sender().start(None).expect("start");
1054        assert!(matches!(client.receiver().try_next(), Ok(FlowEvent::Start { metadata: None })));
1055
1056        let pending = server.sender().reserve_data(256).expect("reserve all slots");
1057        drop(pending);
1058        assert!(matches!(client.receiver().try_next(), Err(Error::WouldBlock)));
1059        assert!(server.sender().reserve_data(256).is_ok());
1060    }
1061
1062    #[test]
1063    fn a_failed_control_commit_returns_its_payload_credit() {
1064        let exchanges = Exchanges::open(
1065            Arc::new(Fleet::join("exchange-failed-commit-test", 2).expect("fleet")),
1066            Incarnation::new(1),
1067            ExchangeSpec::new(
1068                StreamSpec::new(236, 8, CONTROL_FRAME_BYTES),
1069                PayloadArenaSpec::new(237, 1, 64)
1070            )
1071        )
1072        .expect("exchanges");
1073        let (mut server, ticket) = exchanges.create().expect("server");
1074        let mut client = exchanges.open_peer(ticket).expect("client");
1075
1076        server.sender().start(None).expect("start fills control ring");
1077        let mut pending = server.sender().reserve_data(64).expect("reserve only payload slot");
1078        pending.fill(7);
1079        assert!(matches!(pending.commit(), Err(Error::WouldBlock)));
1080
1081        assert!(matches!(client.receiver().try_next(), Ok(FlowEvent::Start { metadata: None })));
1082        let mut retry = server.sender().reserve_data(64).expect("failed commit returned credit");
1083        retry.fill(9);
1084        retry.commit().expect("retry commit");
1085        let received = match client.receiver().try_next().expect("retry data") {
1086            FlowEvent::Data(chunk) => chunk,
1087            _ => panic!("expected retry data")
1088        };
1089        assert_eq!(&*received, &[9; 64]);
1090    }
1091
1092    #[test]
1093    fn a_short_io_commit_returns_unused_trailing_slots() {
1094        let exchanges = exchanges();
1095        let (mut server, ticket) = exchanges.create().expect("server");
1096        let mut client = exchanges.open_peer(ticket).expect("client");
1097        server.sender().start(None).expect("start");
1098        assert!(matches!(client.receiver().try_next(), Ok(FlowEvent::Start { metadata: None })));
1099
1100        let descriptor = {
1101            let mut pending = server.sender().reserve_data(256).expect("reserve IO capacity");
1102            pending[..37].fill(5);
1103            pending.commit_len(37).expect("commit bytes read")
1104        };
1105        assert_eq!(descriptor.payload_len(), 37);
1106        assert_eq!(descriptor.slot_count(), 1);
1107
1108        let mut following = server.sender().reserve_data(192).expect("trailing slots returned");
1109        following.fill(9);
1110        following.commit().expect("commit following chunk");
1111        let first = match client.receiver().try_next().expect("short data") {
1112            FlowEvent::Data(chunk) => chunk,
1113            _ => panic!("expected short data")
1114        };
1115        assert_eq!(&*first, &[5; 37]);
1116        let second = match client.receiver().try_next().expect("following data") {
1117            FlowEvent::Data(chunk) => chunk,
1118            _ => panic!("expected following data")
1119        };
1120        assert_eq!(&*second, &[9; 192]);
1121    }
1122
1123    #[test]
1124    fn reset_is_terminal_for_only_its_own_flow() {
1125        let exchanges = exchanges();
1126        let (server, ticket) = exchanges.create().expect("server");
1127        let client = exchanges.open_peer(ticket).expect("client");
1128        let (mut request_out, mut response_in) = server.split();
1129        let (mut response_out, mut request_in) = client.split();
1130
1131        request_out.reset(ResetCode::new(41)).expect("request reset");
1132        assert!(matches!(
1133            request_in.try_next(),
1134            Ok(FlowEvent::Reset(code)) if code.get() == 41
1135        ));
1136
1137        response_out.start(None).expect("response remains independent");
1138        assert!(matches!(response_in.try_next(), Ok(FlowEvent::Start { metadata: None })));
1139    }
1140}