Skip to main content

uiharu_core/
node.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Decoders as the executor runs them: a node declares its ports, is started with its ports
4//! bound to queues, and runs as one future that reads through an [`Inbox`] or a
5//! [`SignalCursor`] and writes through an [`Outbox`].
6//!
7//! Every read and write is an `await` point. When input is missing, output is full or the
8//! execution budget is spent, the operation returns `Pending`, the future keeps its state, and
9//! the executor polls it again once the condition has changed. Nothing is consumed by an
10//! operation that did not complete, so a decoder paused on output never reads its input again
11//! or sends an event twice.
12
13use std::any::Any;
14use std::cell::{Cell, Ref, RefCell};
15use std::fmt;
16use std::future::poll_fn;
17use std::pin::Pin;
18use std::rc::Rc;
19use std::sync::Arc;
20use std::task::Poll;
21
22use crate::schema::{InputSpec, OutputSpec, Props};
23use crate::signal::{LogicBlock, SignalCursor};
24use crate::stream::{Broadcast, Event, Item, QueueControl, ReaderId};
25use crate::time::{Tick, TickRange, TimeBase};
26
27/// What a node is waiting for when its future returns `Pending`.
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
29pub enum Wait {
30    /// Nothing: the node has not reported a reason.
31    #[default]
32    Nothing,
33    /// An input queue is empty.
34    Input,
35    /// An output queue is full.
36    Output,
37    /// The budget of this poll is spent; the node can go on at once.
38    Budget,
39}
40
41/// The executor's side of a node: its budget, why it last paused and how much it has done.
42#[derive(Debug, Default)]
43pub struct NodeCx {
44    budget: Cell<u32>,
45    waiting: Cell<Wait>,
46    ops: Cell<u64>,
47}
48
49impl NodeCx {
50    /// A context with no budget yet.
51    pub fn new() -> Rc<Self> {
52        Rc::new(Self::default())
53    }
54
55    /// Gives the node `budget` operations for its next poll and clears the wait reason.
56    pub fn refill(&self, budget: u32) {
57        self.budget.set(budget);
58        self.waiting.set(Wait::Nothing);
59    }
60
61    /// Whether an operation may complete now.
62    pub fn has_budget(&self) -> bool {
63        self.budget.get() > 0
64    }
65
66    /// Records a completed operation.
67    pub fn spend(&self) {
68        self.budget.set(self.budget.get().saturating_sub(1));
69        self.ops.set(self.ops.get() + 1);
70    }
71
72    /// Records why the node is about to return `Pending`.
73    pub fn wait(&self, on: Wait) {
74        self.waiting.set(on);
75    }
76
77    /// Why the node last returned `Pending`.
78    pub fn waiting(&self) -> Wait {
79        self.waiting.get()
80    }
81
82    /// Operations completed over the node's lifetime.
83    pub fn ops(&self) -> u64 {
84        self.ops.get()
85    }
86}
87
88/// Where an event comes from: the ticks it covers and when it became certain.
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub struct Provenance {
91    /// The ticks the event covers.
92    pub span: TickRange,
93    /// The tick at which the result became certain.
94    pub settled: Tick,
95}
96
97impl Provenance {
98    /// A result decided at `tick` that covers no ticks.
99    pub const fn at(tick: Tick) -> Self {
100        Self {
101            span: TickRange::at(tick),
102            settled: tick,
103        }
104    }
105
106    /// A result covering `span`, certain at `settled`.
107    pub const fn over(span: TickRange, settled: Tick) -> Self {
108        Self { span, settled }
109    }
110
111    /// A result covering the single tick `tick`.
112    pub const fn one(tick: Tick) -> Self {
113        Self {
114            span: TickRange::one(tick),
115            settled: tick,
116        }
117    }
118
119    /// The provenance of a result built from both: the union of the spans, certain when the
120    /// later of the two was.
121    pub fn join(self, other: Provenance) -> Provenance {
122        Provenance {
123            span: self.span.join(other.span),
124            settled: self.settled.max(other.settled),
125        }
126    }
127}
128
129/// A queue shared between the graph, the executor and the nodes reading and writing it.
130pub(crate) type SharedQueue<T> = Rc<RefCell<Broadcast<T>>>;
131
132/// A node's reader on one of its input queues.
133pub struct Inbox<T> {
134    queue: SharedQueue<T>,
135    reader: ReaderId,
136    cx: Rc<NodeCx>,
137}
138
139impl<T> fmt::Debug for Inbox<T> {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        f.debug_struct("Inbox")
142            .field("reader", &self.reader)
143            .finish_non_exhaustive()
144    }
145}
146
147impl<T> Inbox<T> {
148    pub(crate) fn new(queue: SharedQueue<T>, cx: Rc<NodeCx>) -> Self {
149        let reader = queue.borrow_mut().add_reader();
150        Self { queue, reader, cx }
151    }
152
153    /// The node context the inbox charges.
154    pub fn cx(&self) -> &Rc<NodeCx> {
155        &self.cx
156    }
157
158    /// The next item, if one is waiting; otherwise `Pending`, with the node set to wait for
159    /// input. Does not move past the item and does not charge the budget.
160    pub fn poll_peek(&self) -> Poll<Ref<'_, Item<T>>> {
161        let reader = self.reader;
162        match Ref::filter_map(self.queue.borrow(), |queue| queue.peek(reader)) {
163            Ok(item) => Poll::Ready(item),
164            Err(_) => {
165                self.cx.wait(Wait::Input);
166                Poll::Pending
167            }
168        }
169    }
170
171    /// Moves past the next item. The end of the stream stays.
172    pub fn advance(&self) {
173        self.queue.borrow_mut().advance(self.reader);
174    }
175
176    /// Waits for the next item and reads it through `f` without taking it. Waits for budget
177    /// first, so that a node looking at many items yields like one taking them.
178    pub fn peek_with<'a, R, F>(&'a self, f: F) -> impl Future<Output = R> + 'a
179    where
180        F: FnOnce(&Item<T>) -> R + 'a,
181    {
182        let mut f = Some(f);
183        poll_fn(move |_| {
184            if !self.cx.has_budget() {
185                self.cx.wait(Wait::Budget);
186                return Poll::Pending;
187            }
188            let item = match self.poll_peek() {
189                Poll::Pending => return Poll::Pending,
190                Poll::Ready(item) => item,
191            };
192            match f.take() {
193                Some(f) => Poll::Ready(f(&item)),
194                None => Poll::Pending,
195            }
196        })
197    }
198
199    /// Takes the item last peeked at: moves past it and counts the operation.
200    pub fn take(&self) {
201        self.advance();
202        self.cx.spend();
203    }
204
205    /// The time of the item at the head, if one is waiting; see [`Item::time`].
206    pub fn head_time(&self) -> Option<Tick> {
207        match self.poll_peek() {
208            Poll::Ready(item) => Some(item.time()),
209            Poll::Pending => None,
210        }
211    }
212
213    /// Waits for the next item and takes it; the end of the stream is returned again and
214    /// again.
215    pub fn recv(&mut self) -> impl Future<Output = Item<T>> + '_
216    where
217        T: Clone,
218    {
219        poll_fn(move |_| {
220            if !self.cx.has_budget() {
221                self.cx.wait(Wait::Budget);
222                return Poll::Pending;
223            }
224            let item = match self.poll_peek() {
225                Poll::Pending => return Poll::Pending,
226                Poll::Ready(item) => item.clone(),
227            };
228            self.advance();
229            self.cx.spend();
230            Poll::Ready(item)
231        })
232    }
233}
234
235impl<T> Drop for Inbox<T> {
236    fn drop(&mut self) {
237        self.queue.borrow_mut().remove_reader(self.reader);
238    }
239}
240
241/// An input a node can wait on: an inbox, or a signal cursor.
242pub trait Readable {
243    /// `Ready` when an item (or a block of samples) is there to read without waiting; sets
244    /// the node to wait for input otherwise.
245    fn poll_ready(&self) -> Poll<()>;
246}
247
248impl<T> Readable for Inbox<T> {
249    fn poll_ready(&self) -> Poll<()> {
250        self.poll_peek().map(|_| ())
251    }
252}
253
254/// Waits until every input in `inputs` has something to read. A node with several inputs
255/// waits on all of them, then takes the item whose time ([`Item::time`]) is earliest — the
256/// one with the lowest index on a tie — so that what it does depends on the items alone,
257/// never on which input happened to fill first.
258pub fn ready_all<'a>(inputs: &'a [&'a dyn Readable]) -> impl Future<Output = ()> + 'a {
259    poll_fn(move |_| {
260        let mut ready = true;
261        for input in inputs {
262            ready &= input.poll_ready().is_ready();
263        }
264        if ready {
265            Poll::Ready(())
266        } else {
267            Poll::Pending
268        }
269    })
270}
271
272/// A node's writer on one of its output queues.
273pub struct Outbox<T> {
274    queue: SharedQueue<T>,
275    cx: Rc<NodeCx>,
276    sink: Rc<SinkInner>,
277}
278
279impl<T> fmt::Debug for Outbox<T> {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        f.debug_struct("Outbox").finish_non_exhaustive()
282    }
283}
284
285impl<T: 'static> Outbox<T> {
286    pub(crate) fn new(queue: SharedQueue<T>, cx: Rc<NodeCx>) -> Self {
287        let sink = Rc::new(SinkInner {
288            control: Rc::clone(&queue) as Rc<dyn QueueControl>,
289            cx: Rc::clone(&cx),
290            forwarded: Cell::new(None),
291            held: Cell::new(None),
292        });
293        Self { queue, cx, sink }
294    }
295}
296
297impl<T> Outbox<T> {
298    /// The node context the outbox charges.
299    pub fn cx(&self) -> &Rc<NodeCx> {
300        &self.cx
301    }
302
303    /// Whether the queue would take an item right now.
304    pub fn has_room(&self) -> bool {
305        !self.queue.borrow().is_full()
306    }
307
308    fn poll_push(&mut self, slot: &mut Option<Item<T>>) -> Poll<()> {
309        let Some(item) = slot.take() else {
310            return Poll::Ready(());
311        };
312        if !self.cx.has_budget() {
313            *slot = Some(item);
314            self.cx.wait(Wait::Budget);
315            return Poll::Pending;
316        }
317        match self.queue.borrow_mut().try_push(item) {
318            Ok(()) => {
319                self.cx.spend();
320                Poll::Ready(())
321            }
322            Err(item) => {
323                *slot = Some(item);
324                self.cx.wait(Wait::Output);
325                Poll::Pending
326            }
327        }
328    }
329
330    fn push(&mut self, item: Item<T>) -> impl Future<Output = ()> + '_ {
331        let mut slot = Some(item);
332        poll_fn(move |_| self.poll_push(&mut slot))
333    }
334
335    /// Sends an event, waiting for room in the queue.
336    pub fn emit(&mut self, at: Provenance, payload: T) -> impl Future<Output = ()> + '_ {
337        self.push(Item::Event(Event {
338            seq: 0,
339            span: at.span,
340            settled: at.settled,
341            payload,
342        }))
343    }
344
345    /// Tells the readers that `range` is missing.
346    pub fn gap(&mut self, range: TickRange) -> impl Future<Output = ()> + '_ {
347        self.push(Item::Gap(range))
348    }
349
350    /// Tells the readers that nothing more comes before `tick`.
351    pub fn progress(&mut self, tick: Tick) -> impl Future<Output = ()> + '_ {
352        self.push(Item::Progress(tick))
353    }
354
355    /// Ends the stream at `tick`.
356    pub fn end(&mut self, tick: Tick) -> impl Future<Output = ()> + '_ {
357        self.push(Item::End(tick))
358    }
359
360    /// Where a cursor over a contract passes progress on while the decoder reads a frame
361    /// through it, and where the decoder says what it still holds: this output, without its
362    /// event type. Every call returns a handle to the same state.
363    pub fn progress_sink(&self) -> ProgressSink {
364        ProgressSink(Rc::clone(&self.sink))
365    }
366}
367
368struct SinkInner {
369    control: Rc<dyn QueueControl>,
370    cx: Rc<NodeCx>,
371    /// The latest tick passed on through the sink.
372    forwarded: Cell<Option<Tick>>,
373    /// From where the decoder holds items it has read and not reported yet.
374    held: Cell<Option<Tick>>,
375}
376
377/// An output seen only as a place to pass progress on, for cursors over a contract. A cursor
378/// passes the progress it meets inside a frame on through it, held back to what the decoder
379/// has read through the cursor and not reported yet: what it consumed since it last took the
380/// provenance (`consumed()`), and what it says it still holds ([`ProgressSink::hold`]) when
381/// it took provenance early for an event still to come. Made by [`Outbox::progress_sink`];
382/// clones share their state.
383#[derive(Clone)]
384pub struct ProgressSink(Rc<SinkInner>);
385
386impl fmt::Debug for ProgressSink {
387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388        f.debug_struct("ProgressSink")
389            .field("forwarded", &self.0.forwarded.get())
390            .field("held", &self.0.held.get())
391            .finish()
392    }
393}
394
395impl ProgressSink {
396    /// Passes `Progress(tick)` on, waiting for budget and for room like [`Outbox::progress`];
397    /// a tick at or before one already passed on is not sent again.
398    pub fn progress(&self, tick: Tick) -> impl Future<Output = ()> + '_ {
399        let inner = &*self.0;
400        let mut pending = !inner.forwarded.get().is_some_and(|last| last >= tick);
401        poll_fn(move |_| {
402            if !pending {
403                return Poll::Ready(());
404            }
405            if !inner.cx.has_budget() {
406                inner.cx.wait(Wait::Budget);
407                return Poll::Pending;
408            }
409            if inner.control.try_progress(tick) {
410                inner.cx.spend();
411                inner.forwarded.set(Some(tick));
412                pending = false;
413                Poll::Ready(())
414            } else {
415                inner.cx.wait(Wait::Output);
416                Poll::Pending
417            }
418        })
419    }
420
421    /// The latest tick passed on through this sink.
422    pub fn forwarded(&self) -> Option<Tick> {
423        self.0.forwarded.get()
424    }
425
426    /// Tells from which tick on the decoder still holds items it read through a cursor and
427    /// has not reported — provenance it took early for an event still to come, such as a batch
428    /// being filled — so that progress met inside a frame is held back to there; `None` once
429    /// nothing is held. A decoder that takes provenance only when it emits never needs this.
430    pub fn hold(&self, from: Option<Tick>) {
431        self.0.held.set(from);
432    }
433
434    /// From where the decoder says it still holds items; see [`ProgressSink::hold`].
435    pub fn held(&self) -> Option<Tick> {
436        self.0.held.get()
437    }
438}
439
440#[derive(Clone, Debug)]
441pub struct NodeSpec {
442    /// The node's name, for diagnostics and statistics.
443    pub name: String,
444    /// Its input ports.
445    pub inputs: Vec<InputSpec>,
446    /// Its output ports.
447    pub outputs: Vec<OutputSpec>,
448}
449
450/// The future that runs a node.
451pub type NodeFuture = Pin<Box<dyn Future<Output = ()>>>;
452
453/// A decoder, or any other step of a decode graph.
454pub trait Node: 'static {
455    /// The node's ports.
456    fn spec(&self) -> NodeSpec;
457
458    /// Binds the ports and returns the future that runs the node until its input ends.
459    fn start(self: Box<Self>, io: Io) -> NodeFuture;
460}
461
462impl Node for Box<dyn Node> {
463    fn spec(&self) -> NodeSpec {
464        (**self).spec()
465    }
466
467    fn start(self: Box<Self>, io: Io) -> NodeFuture {
468        (*self).start(io)
469    }
470}
471
472/// The queues bound to a node's ports, handed to [`Node::start`], with what the node may need
473/// to know about them: the time base its inputs are on and the properties of the outputs they
474/// read.
475pub struct Io {
476    cx: Rc<NodeCx>,
477    inputs: Vec<Option<Rc<dyn Any>>>,
478    outputs: Vec<Option<Rc<dyn Any>>>,
479    time_base: Option<TimeBase>,
480    input_props: Vec<Props>,
481}
482
483impl fmt::Debug for Io {
484    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485        f.debug_struct("Io")
486            .field("inputs", &self.inputs.len())
487            .field("outputs", &self.outputs.len())
488            .finish_non_exhaustive()
489    }
490}
491
492impl Io {
493    pub(crate) fn new(
494        cx: Rc<NodeCx>,
495        inputs: Vec<Rc<dyn Any>>,
496        outputs: Vec<Rc<dyn Any>>,
497        time_base: Option<TimeBase>,
498        input_props: Vec<Props>,
499    ) -> Self {
500        Self {
501            cx,
502            inputs: inputs.into_iter().map(Some).collect(),
503            outputs: outputs.into_iter().map(Some).collect(),
504            time_base,
505            input_props,
506        }
507    }
508
509    /// The clock of the source the node's inputs come from; `None` for a node without inputs.
510    /// Decoders turn bit times and timeouts into ticks with it.
511    pub fn time_base(&self) -> Option<TimeBase> {
512        self.time_base
513    }
514
515    /// The properties the output connected to input port `index` declares: what the bus
516    /// promised, such as its word width.
517    ///
518    /// # Panics
519    ///
520    /// When the node declared no such port.
521    pub fn input_props(&self, index: usize) -> &Props {
522        self.input_props
523            .get(index)
524            .unwrap_or_else(|| panic!("the node declared no input port {index}"))
525    }
526
527    /// The node context.
528    pub fn cx(&self) -> &Rc<NodeCx> {
529        &self.cx
530    }
531
532    fn take<T: 'static>(
533        slots: &mut [Option<Rc<dyn Any>>],
534        index: usize,
535        what: &str,
536    ) -> SharedQueue<T> {
537        let slot = slots
538            .get_mut(index)
539            .unwrap_or_else(|| panic!("the node declared no {what} port {index}"))
540            .take()
541            .unwrap_or_else(|| panic!("{what} port {index} was bound twice"));
542        slot.downcast::<RefCell<Broadcast<T>>>()
543            .unwrap_or_else(|_| panic!("{what} port {index} carries another event type"))
544    }
545
546    /// The reader on input port `index`.
547    ///
548    /// # Panics
549    ///
550    /// When the node declared no such port, bound it already, or declared it with another
551    /// event type than `T`: all mistakes in the node itself, not in the graph.
552    pub fn input<T: 'static>(&mut self, index: usize) -> Inbox<T> {
553        Inbox::new(
554            Self::take(&mut self.inputs, index, "input"),
555            Rc::clone(&self.cx),
556        )
557    }
558
559    /// Input port `index` as a cursor over logic samples.
560    ///
561    /// # Panics
562    ///
563    /// As [`Io::input`] does.
564    pub fn signal(&mut self, index: usize) -> SignalCursor {
565        SignalCursor::new(self.input::<Arc<LogicBlock>>(index))
566    }
567
568    /// The writer on output port `index`.
569    ///
570    /// # Panics
571    ///
572    /// As [`Io::input`] does.
573    pub fn output<T: 'static>(&mut self, index: usize) -> Outbox<T> {
574        Outbox::new(
575            Self::take(&mut self.outputs, index, "output"),
576            Rc::clone(&self.cx),
577        )
578    }
579}
580
581#[cfg(test)]
582mod tests {
583    use std::task::{Context, Waker};
584
585    use super::*;
586
587    fn shared<T>(capacity: usize) -> SharedQueue<T> {
588        Rc::new(RefCell::new(Broadcast::new(capacity)))
589    }
590
591    fn poll<F: Future>(future: Pin<&mut F>) -> Poll<F::Output> {
592        future.poll(&mut Context::from_waker(Waker::noop()))
593    }
594
595    #[test]
596    fn reads_wait_for_input_and_writes_for_room() {
597        let queue = shared::<u8>(1);
598        let cx = NodeCx::new();
599        cx.refill(10);
600        let mut inbox = Inbox::new(Rc::clone(&queue), Rc::clone(&cx));
601        let mut outbox = Outbox::new(Rc::clone(&queue), Rc::clone(&cx));
602
603        let mut read = Box::pin(inbox.recv());
604        assert_eq!(poll(read.as_mut()), Poll::Pending);
605        assert_eq!(cx.waiting(), Wait::Input);
606        drop(read);
607
608        let mut write = Box::pin(outbox.emit(Provenance::at(Tick(3)), 7));
609        assert_eq!(poll(write.as_mut()), Poll::Ready(()));
610        drop(write);
611        let mut write = Box::pin(outbox.emit(Provenance::at(Tick(4)), 8));
612        assert_eq!(poll(write.as_mut()), Poll::Pending);
613        assert_eq!(cx.waiting(), Wait::Output);
614        drop(write);
615
616        let mut read = Box::pin(inbox.recv());
617        match poll(read.as_mut()) {
618            Poll::Ready(Item::Event(event)) => {
619                assert_eq!((event.seq, event.payload, event.settled), (0, 7, Tick(3)));
620            }
621            other => panic!("expected the event, got {other:?}"),
622        }
623        drop(read);
624        assert_eq!(cx.ops(), 2);
625    }
626
627    #[test]
628    fn a_spent_budget_pauses_without_consuming() {
629        let queue = shared::<u8>(4);
630        let cx = NodeCx::new();
631        cx.refill(1);
632        let mut inbox = Inbox::new(Rc::clone(&queue), Rc::clone(&cx));
633        let mut outbox = Outbox::new(Rc::clone(&queue), Rc::clone(&cx));
634        let mut write = Box::pin(outbox.emit(Provenance::at(Tick(0)), 1));
635        assert_eq!(poll(write.as_mut()), Poll::Ready(()));
636        drop(write);
637        let mut write = Box::pin(outbox.emit(Provenance::at(Tick(0)), 2));
638        assert_eq!(poll(write.as_mut()), Poll::Pending);
639        assert_eq!(cx.waiting(), Wait::Budget);
640        assert_eq!(queue.borrow().len(), 1);
641        drop(write);
642        cx.refill(1);
643        let mut read = Box::pin(inbox.recv());
644        assert!(matches!(poll(read.as_mut()), Poll::Ready(Item::Event(_))));
645        drop(read);
646        assert!(matches!(
647            poll(Box::pin(inbox.recv()).as_mut()),
648            Poll::Pending
649        ));
650        assert_eq!(cx.waiting(), Wait::Budget);
651    }
652
653    #[test]
654    fn a_sink_passes_progress_on_once_and_waits_for_room() {
655        let queue = shared::<u8>(1);
656        let cx = NodeCx::new();
657        cx.refill(10);
658        let mut inbox = Inbox::new(Rc::clone(&queue), Rc::clone(&cx));
659        let mut outbox = Outbox::new(Rc::clone(&queue), Rc::clone(&cx));
660        let sink = outbox.progress_sink();
661        assert_eq!(
662            poll(Box::pin(sink.progress(Tick(5))).as_mut()),
663            Poll::Ready(())
664        );
665        assert_eq!(sink.forwarded(), Some(Tick(5)));
666        // A tick at or before the one passed on is not sent again; a later one folds into it.
667        assert_eq!(
668            poll(Box::pin(sink.progress(Tick(3))).as_mut()),
669            Poll::Ready(())
670        );
671        assert_eq!(
672            poll(Box::pin(sink.progress(Tick(7))).as_mut()),
673            Poll::Ready(())
674        );
675        assert_eq!(queue.borrow().len(), 1);
676        assert!(matches!(
677            poll(Box::pin(inbox.recv()).as_mut()),
678            Poll::Ready(Item::Progress(Tick(7)))
679        ));
680        // Behind an event in a full queue, progress waits for room like any item.
681        assert_eq!(
682            poll(Box::pin(outbox.emit(Provenance::at(Tick(8)), 1)).as_mut()),
683            Poll::Ready(())
684        );
685        assert_eq!(
686            poll(Box::pin(sink.progress(Tick(9))).as_mut()),
687            Poll::Pending
688        );
689        assert_eq!(cx.waiting(), Wait::Output);
690        assert!(matches!(
691            poll(Box::pin(inbox.recv()).as_mut()),
692            Poll::Ready(Item::Event(_))
693        ));
694        assert_eq!(
695            poll(Box::pin(sink.progress(Tick(9))).as_mut()),
696            Poll::Ready(())
697        );
698        // Every sink of an output shares what the decoder says it holds.
699        let other = outbox.progress_sink();
700        other.hold(Some(Tick(4)));
701        assert_eq!(sink.held(), Some(Tick(4)));
702        sink.hold(None);
703        assert_eq!(other.held(), None);
704    }
705
706    #[test]
707    fn dropping_an_inbox_detaches_its_reader() {
708        let queue = shared::<u8>(1);
709        let cx = NodeCx::new();
710        cx.refill(10);
711        let inbox = Inbox::new(Rc::clone(&queue), Rc::clone(&cx));
712        let mut outbox = Outbox::new(Rc::clone(&queue), Rc::clone(&cx));
713        assert_eq!(
714            poll(Box::pin(outbox.emit(Provenance::at(Tick(0)), 1)).as_mut()),
715            Poll::Ready(())
716        );
717        assert!(!outbox.has_room());
718        drop(inbox);
719        assert!(outbox.has_room());
720    }
721}