Skip to main content

datum/concurrent/
signal.rs

1use std::{
2    collections::HashMap,
3    hint,
4    marker::PhantomData,
5    sync::{
6        Arc, Condvar, Mutex, MutexGuard,
7        atomic::{AtomicBool, AtomicU8, AtomicU64, AtomicUsize, Ordering},
8        mpsc,
9    },
10    thread,
11    time::Duration,
12};
13
14use arc_swap::ArcSwap;
15use ractor::{Actor, ActorProcessingErr, ActorRef};
16use tokio::sync::Notify;
17
18use crate::{
19    StreamError, StreamResult,
20    actor::block_on_ractor_runtime,
21    stream::{BoxStream, NotUsed, Source, current_stream_cancelled},
22};
23
24const SLOT_WAIT_BACKSTOP: Duration = Duration::from_millis(10);
25const ASYNC_SLOT_WAIT_BACKSTOP: Duration = Duration::from_micros(50);
26const STATE_OPEN: u8 = 0;
27const STATE_CLOSING: u8 = 1;
28const STATE_CLOSED: u8 = 2;
29const NO_SEQUENCE: u64 = u64::MAX;
30const SLOT_OPEN: u8 = 0;
31const SLOT_COMPLETE: u8 = 1;
32const SLOT_ERROR: u8 = 2;
33
34type Ack = mpsc::Sender<StreamResult<()>>;
35
36/// Latest-value state cell with a lock-free synchronous snapshot and a coalesced change feed.
37///
38/// `Signal` has a two-plane implementation. The data plane (`set`, `set_eventually`, `update`,
39/// and `update_eventually`) runs on the caller thread: it publishes the `ArcSwap` mirror, stores
40/// the published sequence, wakes currently parked subscribers, and then returns. The Ractor actor
41/// owns only the control plane: subscribe, unsubscribe, close, and terminal delivery.
42///
43/// `changes()` is coalesced. Each materialized subscriber keeps only a consumed sequence cursor and
44/// reads the latest global snapshot when pulled; slow subscribers skip intermediate values and
45/// writers are never backpressured. Subscribe has no get-then-subscribe gap: the actor publishes
46/// the new slot-table snapshot before the materialized stream can pull, and the first pull reads
47/// the latest global `mirror + published_sequence` snapshot.
48///
49/// `update` uses `ArcSwap::compare_and_swap` under the write publication turn. The update function
50/// may be re-invoked if a concurrent writer wins the CAS race, matching the usual Ref/atomic
51/// update contract.
52///
53/// Values are stored as immutable `Arc<T>` snapshots. Datum cannot prevent user-chosen interior
54/// mutability inside `T`; callers should treat values placed in a `Signal` as logically immutable.
55pub struct Signal<T: Send + Sync + 'static> {
56    inner: Arc<SignalInner<T>>,
57}
58
59struct SignalInner<T: Send + Sync + 'static> {
60    actor: ActorRef<SignalMessage<T>>,
61    shared: Arc<SignalShared<T>>,
62    next_subscriber_id: Arc<AtomicU64>,
63}
64
65struct SignalShared<T: Send + Sync + 'static> {
66    mirror: Arc<ArcSwap<T>>,
67    subscribers: Arc<ArcSwap<SignalSlotTable<T>>>,
68    parked_slots: Arc<AtomicUsize>,
69    lifecycle: AtomicU8,
70    active_writers: AtomicUsize,
71    next_sequence: AtomicU64,
72    published_sequence: Arc<AtomicU64>,
73    delivered_sequence: AtomicU64,
74}
75
76struct SignalSlotTable<T: Send + Sync + 'static> {
77    slots: Vec<Arc<SignalSlot<T>>>,
78}
79
80impl<T: Send + Sync + 'static> Clone for Signal<T> {
81    fn clone(&self) -> Self {
82        Self {
83            inner: Arc::clone(&self.inner),
84        }
85    }
86}
87
88impl<T: Send + Sync + 'static> Signal<T> {
89    /// Create a new signal initialized to `initial`.
90    pub fn new(initial: T) -> StreamResult<Self> {
91        let value = Arc::new(initial);
92        let shared = Arc::new(SignalShared {
93            mirror: Arc::new(ArcSwap::from(Arc::clone(&value))),
94            subscribers: Arc::new(ArcSwap::from_pointee(SignalSlotTable { slots: Vec::new() })),
95            parked_slots: Arc::new(AtomicUsize::new(0)),
96            lifecycle: AtomicU8::new(STATE_OPEN),
97            active_writers: AtomicUsize::new(0),
98            next_sequence: AtomicU64::new(0),
99            published_sequence: Arc::new(AtomicU64::new(0)),
100            delivered_sequence: AtomicU64::new(0),
101        });
102        let state = SignalActorState {
103            shared: Arc::clone(&shared),
104            subscribers: HashMap::new(),
105            closed: false,
106        };
107        let (actor, _handle) =
108            block_on_ractor_runtime(Actor::spawn(None, SignalActor::<T>::default(), state))?
109                .map_err(|error| {
110                    StreamError::Failed(format!("signal actor failed to spawn: {error}"))
111                })?;
112        Ok(Self {
113            inner: Arc::new(SignalInner {
114                actor,
115                shared,
116                next_subscriber_id: Arc::new(AtomicU64::new(1)),
117            }),
118        })
119    }
120
121    /// Return the current immutable snapshot without sending an actor message.
122    #[must_use]
123    pub fn get(&self) -> Arc<T> {
124        self.inner.shared.mirror.load_full()
125    }
126
127    /// Return a cloned value using `ArcSwap::load()`'s guarded read path.
128    ///
129    /// This avoids cloning the `Arc` itself on the hot read path. For scalar `Copy`/cheap-`Clone`
130    /// values, this is the fair equivalent of JVM refs returning the value directly; use
131    /// [`Signal::get`] when the caller wants an owned snapshot shared by `Arc`.
132    #[must_use]
133    pub fn get_cloned(&self) -> T
134    where
135        T: Clone,
136    {
137        self.inner.shared.mirror.load().as_ref().clone()
138    }
139
140    /// Set the state and return after the value is published to current subscribers.
141    pub fn set(&self, value: T) -> StreamResult<()> {
142        self.publish_set(Arc::new(value))
143    }
144
145    /// Set the state on the caller thread.
146    ///
147    /// In the two-plane implementation this publishes before returning; it remains named
148    /// `set_eventually` for API compatibility with the actor-backed first implementation.
149    pub fn set_eventually(&self, value: T) -> StreamResult<()> {
150        self.publish_set(Arc::new(value))
151    }
152
153    /// Update the state atomically and return after publication.
154    ///
155    /// The update function may be called more than once if a concurrent writer wins the CAS race.
156    pub fn update<F>(&self, update: F) -> StreamResult<()>
157    where
158        F: FnMut(&T) -> T + Send + 'static,
159    {
160        self.publish_update(update)
161    }
162
163    /// Update the state atomically on the caller thread.
164    ///
165    /// The update function may be called more than once if a concurrent writer wins the CAS race.
166    pub fn update_eventually<F>(&self, update: F) -> StreamResult<()>
167    where
168        F: FnMut(&T) -> T + Send + 'static,
169    {
170        self.publish_update(update)
171    }
172
173    /// Close the signal, re-emitting the current final snapshot to current subscribers before
174    /// completing them. Post-close subscribers receive the final snapshot and then completion.
175    pub fn close(&self) -> StreamResult<()> {
176        self.send_close(None)
177    }
178
179    /// Set a final value, then close the signal in one control-plane turn.
180    pub fn close_with(&self, final_value: T) -> StreamResult<()> {
181        self.send_close(Some(final_value))
182    }
183
184    fn publish_set(&self, value: Arc<T>) -> StreamResult<()> {
185        let _permit = self.inner.shared.begin_write("signal")?;
186        let sequence = self.inner.shared.claim_sequence();
187        self.inner.shared.wait_publish_turn(sequence);
188        self.inner.shared.mirror.store(Arc::clone(&value));
189        self.inner.shared.finish_publish(sequence);
190        Ok(())
191    }
192
193    fn publish_update<F>(&self, mut update: F) -> StreamResult<()>
194    where
195        F: FnMut(&T) -> T + Send + 'static,
196    {
197        let _permit = self.inner.shared.begin_write("signal")?;
198        let sequence = self.inner.shared.claim_sequence();
199        self.inner.shared.wait_publish_turn(sequence);
200        loop {
201            let current = self.inner.shared.mirror.load();
202            let next = Arc::new(update(current.as_ref()));
203            let previous = self
204                .inner
205                .shared
206                .mirror
207                .compare_and_swap(&*current, Arc::clone(&next));
208            if std::ptr::eq(current.as_ref(), previous.as_ref()) {
209                break;
210            }
211        }
212        self.inner.shared.finish_publish(sequence);
213        Ok(())
214    }
215
216    fn send_close(&self, final_value: Option<T>) -> StreamResult<()> {
217        let (reply, receiver) = mpsc::channel();
218        self.inner
219            .actor
220            .send_message(SignalMessage::Close { final_value, reply })
221            .map_err(|error| StreamError::ActorAskSendFailed {
222                reason: error.to_string(),
223            })?;
224        receiver.recv().unwrap_or(Err(StreamError::ActorTerminated))
225    }
226
227    fn register_slot(&self, slot: Arc<SignalSlot<T>>, id: u64) -> StreamResult<()> {
228        let (reply, receiver) = mpsc::channel();
229        self.inner
230            .actor
231            .send_message(SignalMessage::Subscribe { id, slot, reply })
232            .map_err(|error| StreamError::ActorAskSendFailed {
233                reason: error.to_string(),
234            })?;
235        receiver.recv().unwrap_or(Err(StreamError::ActorTerminated))
236    }
237}
238
239impl<T: Send + Sync + 'static> SignalShared<T> {
240    fn begin_write(&self, kind: &'static str) -> StreamResult<WritePermit<'_>> {
241        if self.lifecycle.load(Ordering::Acquire) != STATE_OPEN {
242            return Err(closed_error(kind));
243        }
244        self.active_writers.fetch_add(1, Ordering::AcqRel);
245        if self.lifecycle.load(Ordering::Acquire) == STATE_OPEN {
246            Ok(WritePermit {
247                active_writers: &self.active_writers,
248            })
249        } else {
250            self.active_writers.fetch_sub(1, Ordering::AcqRel);
251            Err(closed_error(kind))
252        }
253    }
254
255    fn claim_sequence(&self) -> u64 {
256        self.next_sequence.fetch_add(1, Ordering::AcqRel) + 1
257    }
258
259    fn wait_publish_turn(&self, sequence: u64) {
260        let mut spins = 0_u32;
261        while self.delivered_sequence.load(Ordering::Acquire) + 1 != sequence {
262            spins = spins.wrapping_add(1);
263            if spins < 64 {
264                hint::spin_loop();
265            } else {
266                thread::yield_now();
267            }
268        }
269    }
270
271    fn finish_publish(&self, sequence: u64) {
272        self.published_sequence.store(sequence, Ordering::Release);
273        if self.parked_slots.load(Ordering::Acquire) != 0 {
274            let table = self.subscribers.load();
275            for slot in &table.slots {
276                slot.publish(sequence);
277            }
278        }
279        self.delivered_sequence.store(sequence, Ordering::Release);
280    }
281
282    fn wait_for_writers_to_drain(&self) {
283        while self.active_writers.load(Ordering::Acquire) != 0 {
284            thread::yield_now();
285        }
286    }
287}
288
289struct WritePermit<'a> {
290    active_writers: &'a AtomicUsize,
291}
292
293impl Drop for WritePermit<'_> {
294    fn drop(&mut self) {
295        self.active_writers.fetch_sub(1, Ordering::AcqRel);
296    }
297}
298
299impl<T: Clone + Send + Sync + 'static> Signal<T> {
300    /// A coalesced source of the current value followed by later snapshots.
301    ///
302    /// Each materialization registers a fresh slot with the control actor. Slow subscribers may
303    /// skip intermediate values, but always observe the newest pending snapshot and the final
304    /// snapshot before completion.
305    #[must_use]
306    pub fn changes(&self) -> Source<T> {
307        let actor = self.inner.actor.clone();
308        let signal = self.clone();
309        let next_subscriber_id = Arc::clone(&self.inner.next_subscriber_id);
310        Source::from_materialized_factory(move |_materializer| {
311            let id = next_subscriber_id.fetch_add(1, Ordering::Relaxed);
312            let slot = SignalSlot::new(
313                id,
314                actor.clone(),
315                Arc::clone(&signal.inner.shared.mirror),
316                Arc::clone(&signal.inner.shared.published_sequence),
317                Arc::clone(&signal.inner.shared.parked_slots),
318            );
319            signal.register_slot(Arc::clone(&slot), id)?;
320            let stream: BoxStream<T> = Box::new(SignalChangesStream {
321                slot,
322                terminated: false,
323            });
324            Ok((stream, NotUsed))
325        })
326    }
327
328    #[doc(hidden)]
329    pub fn __benchmark_changes(&self) -> StreamResult<SignalBenchmarkStream<T>> {
330        let id = self
331            .inner
332            .next_subscriber_id
333            .fetch_add(1, Ordering::Relaxed);
334        let slot = SignalSlot::new(
335            id,
336            self.inner.actor.clone(),
337            Arc::clone(&self.inner.shared.mirror),
338            Arc::clone(&self.inner.shared.published_sequence),
339            Arc::clone(&self.inner.shared.parked_slots),
340        );
341        Ok(SignalBenchmarkStream {
342            slot,
343            terminated: false,
344        })
345    }
346}
347
348impl<T: Send + Sync + 'static> Drop for SignalInner<T> {
349    fn drop(&mut self) {
350        self.actor.stop(None);
351    }
352}
353
354enum SignalMessage<T: Send + Sync + 'static> {
355    Close {
356        final_value: Option<T>,
357        reply: Ack,
358    },
359    Subscribe {
360        id: u64,
361        slot: Arc<SignalSlot<T>>,
362        reply: Ack,
363    },
364    Unsubscribe {
365        id: u64,
366    },
367}
368
369struct SignalActor<T> {
370    _marker: PhantomData<fn() -> T>,
371}
372
373impl<T> Default for SignalActor<T> {
374    fn default() -> Self {
375        Self {
376            _marker: PhantomData,
377        }
378    }
379}
380
381struct SignalActorState<T: Send + Sync + 'static> {
382    shared: Arc<SignalShared<T>>,
383    subscribers: HashMap<u64, Arc<SignalSlot<T>>>,
384    closed: bool,
385}
386
387impl<T: Send + Sync + 'static> Actor for SignalActor<T> {
388    type Msg = SignalMessage<T>;
389    type State = SignalActorState<T>;
390    type Arguments = SignalActorState<T>;
391
392    async fn pre_start(
393        &self,
394        _myself: ActorRef<Self::Msg>,
395        args: Self::Arguments,
396    ) -> Result<Self::State, ActorProcessingErr> {
397        Ok(args)
398    }
399
400    async fn handle(
401        &self,
402        _myself: ActorRef<Self::Msg>,
403        message: Self::Msg,
404        state: &mut Self::State,
405    ) -> Result<(), ActorProcessingErr> {
406        match message {
407            SignalMessage::Close { final_value, reply } => {
408                close_signal(state, final_value);
409                let _ = reply.send(Ok(()));
410            }
411            SignalMessage::Subscribe { id, slot, reply } => {
412                if state.closed || state.shared.lifecycle.load(Ordering::Acquire) == STATE_CLOSED {
413                    slot.complete_with_final();
414                } else {
415                    state.subscribers.insert(id, Arc::clone(&slot));
416                    publish_signal_slot_table(state);
417                }
418                let _ = reply.send(Ok(()));
419            }
420            SignalMessage::Unsubscribe { id } => {
421                state.subscribers.remove(&id);
422                publish_signal_slot_table(state);
423            }
424        }
425        Ok(())
426    }
427
428    async fn post_stop(
429        &self,
430        _myself: ActorRef<Self::Msg>,
431        state: &mut Self::State,
432    ) -> Result<(), ActorProcessingErr> {
433        if !state.closed {
434            for slot in state.subscribers.values() {
435                slot.fail(StreamError::ActorTerminated);
436            }
437            state.subscribers.clear();
438            publish_signal_slot_table(state);
439        }
440        Ok(())
441    }
442}
443
444fn close_signal<T: Send + Sync + 'static>(state: &mut SignalActorState<T>, final_value: Option<T>) {
445    if state.closed {
446        return;
447    }
448    match state.shared.lifecycle.compare_exchange(
449        STATE_OPEN,
450        STATE_CLOSING,
451        Ordering::AcqRel,
452        Ordering::Acquire,
453    ) {
454        Ok(_) => {}
455        Err(STATE_CLOSED) => {
456            state.closed = true;
457            return;
458        }
459        Err(_) => {}
460    }
461    state.shared.wait_for_writers_to_drain();
462
463    if let Some(final_value) = final_value {
464        let value = Arc::new(final_value);
465        let sequence = state.shared.claim_sequence();
466        state.shared.wait_publish_turn(sequence);
467        state.shared.mirror.store(Arc::clone(&value));
468        state
469            .shared
470            .published_sequence
471            .store(sequence, Ordering::Release);
472        state
473            .shared
474            .delivered_sequence
475            .store(sequence, Ordering::Release);
476    }
477    state
478        .shared
479        .lifecycle
480        .store(STATE_CLOSED, Ordering::Release);
481
482    for slot in state.subscribers.values() {
483        slot.complete_with_final();
484    }
485    state.subscribers.clear();
486    publish_signal_slot_table(state);
487    state.closed = true;
488}
489
490fn publish_signal_slot_table<T: Send + Sync + 'static>(state: &SignalActorState<T>) {
491    let slots = state.subscribers.values().cloned().collect::<Vec<_>>();
492    state
493        .shared
494        .subscribers
495        .store(Arc::new(SignalSlotTable { slots }));
496}
497
498fn closed_error(kind: &str) -> StreamError {
499    StreamError::Failed(format!("{kind} is closed"))
500}
501
502struct SignalSlot<T: Send + Sync + 'static> {
503    id: u64,
504    actor: ActorRef<SignalMessage<T>>,
505    mirror: Arc<ArcSwap<T>>,
506    published_sequence: Arc<AtomicU64>,
507    parked_slots: Arc<AtomicUsize>,
508    parked: AtomicBool,
509    consumed_sequence: AtomicU64,
510    terminal_state: AtomicU8,
511    terminal: Mutex<Option<SignalSlotTerminal>>,
512    available: Condvar,
513    async_available: Notify,
514}
515
516#[derive(Clone)]
517enum SignalSlotTerminal {
518    Complete,
519    Error(StreamError),
520}
521
522impl<T: Send + Sync + 'static> SignalSlot<T> {
523    fn new(
524        id: u64,
525        actor: ActorRef<SignalMessage<T>>,
526        mirror: Arc<ArcSwap<T>>,
527        published_sequence: Arc<AtomicU64>,
528        parked_slots: Arc<AtomicUsize>,
529    ) -> Arc<Self> {
530        Arc::new(Self {
531            id,
532            actor,
533            mirror,
534            published_sequence,
535            parked_slots,
536            parked: AtomicBool::new(false),
537            consumed_sequence: AtomicU64::new(NO_SEQUENCE),
538            terminal_state: AtomicU8::new(SLOT_OPEN),
539            terminal: Mutex::new(None),
540            available: Condvar::new(),
541            async_available: Notify::new(),
542        })
543    }
544
545    fn terminal_lock(&self) -> MutexGuard<'_, Option<SignalSlotTerminal>> {
546        self.terminal
547            .lock()
548            .unwrap_or_else(|poison| poison.into_inner())
549    }
550
551    fn publish(&self, sequence: u64) {
552        if self.terminal_state.load(Ordering::Acquire) != SLOT_OPEN {
553            return;
554        }
555
556        let was_consumed = !has_unconsumed(
557            sequence.saturating_sub(1),
558            self.consumed_sequence.load(Ordering::Acquire),
559        );
560        if was_consumed {
561            self.wake();
562        }
563    }
564
565    fn complete_with_final(&self) {
566        if self
567            .terminal_state
568            .compare_exchange(
569                SLOT_OPEN,
570                SLOT_COMPLETE,
571                Ordering::AcqRel,
572                Ordering::Acquire,
573            )
574            .is_err()
575        {
576            return;
577        }
578        *self.terminal_lock() = Some(SignalSlotTerminal::Complete);
579        self.consumed_sequence.store(NO_SEQUENCE, Ordering::Release);
580        self.wake();
581    }
582
583    fn fail(&self, error: StreamError) {
584        if self
585            .terminal_state
586            .compare_exchange(SLOT_OPEN, SLOT_ERROR, Ordering::AcqRel, Ordering::Acquire)
587            .is_err()
588        {
589            return;
590        }
591        *self.terminal_lock() = Some(SignalSlotTerminal::Error(error));
592        self.wake();
593    }
594
595    fn take_value(&self) -> Option<Arc<T>> {
596        loop {
597            let available = self.published_sequence.load(Ordering::Acquire);
598            let consumed = self.consumed_sequence.load(Ordering::Acquire);
599            if !has_unconsumed(available, consumed) {
600                return None;
601            }
602            if self
603                .consumed_sequence
604                .compare_exchange(consumed, available, Ordering::AcqRel, Ordering::Acquire)
605                .is_err()
606            {
607                continue;
608            }
609
610            return Some(self.mirror.load_full());
611        }
612    }
613
614    fn terminal(&self) -> Option<SignalSlotTerminal> {
615        if self.terminal_state.load(Ordering::Acquire) == SLOT_OPEN {
616            return None;
617        }
618        self.terminal_lock().clone()
619    }
620
621    fn park(&self) {
622        if !self.parked.swap(true, Ordering::AcqRel) {
623            self.parked_slots.fetch_add(1, Ordering::AcqRel);
624        }
625    }
626
627    fn unpark(&self) {
628        if self.parked.swap(false, Ordering::AcqRel) {
629            self.parked_slots.fetch_sub(1, Ordering::AcqRel);
630        }
631    }
632
633    fn wake(&self) {
634        self.unpark();
635        self.available.notify_all();
636        self.async_available.notify_waiters();
637    }
638
639    fn unsubscribe(&self) {
640        let _ = self
641            .actor
642            .send_message(SignalMessage::Unsubscribe { id: self.id });
643    }
644}
645
646fn has_unconsumed(available: u64, consumed: u64) -> bool {
647    available != NO_SEQUENCE && (consumed == NO_SEQUENCE || available > consumed)
648}
649
650struct SignalChangesStream<T: Clone + Send + Sync + 'static> {
651    slot: Arc<SignalSlot<T>>,
652    terminated: bool,
653}
654
655#[doc(hidden)]
656pub struct SignalBenchmarkStream<T: Clone + Send + Sync + 'static> {
657    slot: Arc<SignalSlot<T>>,
658    terminated: bool,
659}
660
661impl<T: Clone + Send + Sync + 'static> Iterator for SignalChangesStream<T> {
662    type Item = StreamResult<T>;
663
664    fn next(&mut self) -> Option<Self::Item> {
665        if self.terminated {
666            return None;
667        }
668
669        loop {
670            if let Some(value) = self.slot.take_value() {
671                return Some(Ok(value.as_ref().clone()));
672            }
673            if let Some(terminal) = self.slot.terminal() {
674                self.terminated = true;
675                return match terminal {
676                    SignalSlotTerminal::Complete => None,
677                    SignalSlotTerminal::Error(error) => Some(Err(error)),
678                };
679            }
680
681            if current_stream_cancelled()
682                .as_ref()
683                .is_some_and(|cancelled| cancelled.load(Ordering::SeqCst))
684            {
685                self.terminated = true;
686                return Some(Err(StreamError::Cancelled));
687            }
688            self.slot.park();
689            if let Some(value) = self.slot.take_value() {
690                self.slot.unpark();
691                return Some(Ok(value.as_ref().clone()));
692            }
693            if let Some(terminal) = self.slot.terminal() {
694                self.slot.unpark();
695                self.terminated = true;
696                return match terminal {
697                    SignalSlotTerminal::Complete => None,
698                    SignalSlotTerminal::Error(error) => Some(Err(error)),
699                };
700            }
701            let guard = self.slot.terminal_lock();
702            let _guard = self
703                .slot
704                .available
705                .wait_timeout(guard, SLOT_WAIT_BACKSTOP)
706                .unwrap_or_else(|poison| poison.into_inner())
707                .0;
708            self.slot.unpark();
709        }
710    }
711}
712
713impl<T: Clone + Send + Sync + 'static> Drop for SignalChangesStream<T> {
714    fn drop(&mut self) {
715        self.slot.unsubscribe();
716    }
717}
718
719impl<T: Clone + Send + Sync + 'static> SignalBenchmarkStream<T> {
720    #[doc(hidden)]
721    pub async fn next(&mut self) -> Option<StreamResult<T>> {
722        if self.terminated {
723            return None;
724        }
725
726        loop {
727            let notified = self.slot.async_available.notified();
728            tokio::pin!(notified);
729            notified.as_mut().enable();
730
731            if let Some(value) = self.slot.take_value() {
732                self.slot.unpark();
733                return Some(Ok(value.as_ref().clone()));
734            }
735            if let Some(terminal) = self.slot.terminal() {
736                self.slot.unpark();
737                self.terminated = true;
738                return match terminal {
739                    SignalSlotTerminal::Complete => None,
740                    SignalSlotTerminal::Error(error) => Some(Err(error)),
741                };
742            }
743
744            {
745                self.slot.park();
746                if let Some(value) = self.slot.take_value() {
747                    self.slot.unpark();
748                    return Some(Ok(value.as_ref().clone()));
749                }
750                if let Some(terminal) = self.slot.terminal() {
751                    self.slot.unpark();
752                    self.terminated = true;
753                    return match terminal {
754                        SignalSlotTerminal::Complete => None,
755                        SignalSlotTerminal::Error(error) => Some(Err(error)),
756                    };
757                }
758            }
759
760            let _ = tokio::time::timeout(ASYNC_SLOT_WAIT_BACKSTOP, notified.as_mut()).await;
761            self.slot.unpark();
762        }
763    }
764}
765
766impl<T: Clone + Send + Sync + 'static> Drop for SignalBenchmarkStream<T> {
767    fn drop(&mut self) {
768        self.slot.unpark();
769    }
770}
771
772#[cfg(test)]
773mod tests {
774    use super::*;
775    use crate::{Sink, stream::Materializer};
776    use std::{
777        sync::{
778            Barrier,
779            atomic::{AtomicBool, AtomicUsize},
780        },
781        thread,
782        time::{Duration, Instant},
783    };
784
785    fn wait<T>(completion: crate::StreamCompletion<T>) -> T {
786        completion.wait().unwrap()
787    }
788
789    fn wait_until<F>(timeout: Duration, mut condition: F) -> bool
790    where
791        F: FnMut() -> bool,
792    {
793        let deadline = Instant::now() + timeout;
794        while Instant::now() < deadline {
795            if condition() {
796                return true;
797            }
798            thread::yield_now();
799        }
800        condition()
801    }
802
803    #[test]
804    fn get_snapshot_and_acked_set_read_your_writes() {
805        let signal = Signal::new(1_u64).unwrap();
806        assert_eq!(*signal.get(), 1);
807        assert_eq!(signal.get_cloned(), 1);
808        signal.set(2).unwrap();
809        assert_eq!(*signal.get(), 2);
810        assert_eq!(signal.get_cloned(), 2);
811        signal.update(|value| *value + 1).unwrap();
812        assert_eq!(*signal.get(), 3);
813        assert_eq!(signal.get_cloned(), 3);
814    }
815
816    #[test]
817    fn subscribe_sees_current_then_changes() {
818        let signal = Signal::new(10_u64).unwrap();
819        let queue = signal.changes().run_with(Sink::queue()).unwrap();
820        assert_eq!(queue.pull().unwrap(), Some(10));
821        signal.set(11).unwrap();
822        assert_eq!(queue.pull().unwrap(), Some(11));
823        signal.set(12).unwrap();
824        assert_eq!(queue.pull().unwrap(), Some(12));
825    }
826
827    #[test]
828    fn subscribe_has_no_get_then_subscribe_gap_under_concurrent_sets() {
829        const RUNS: usize = 128;
830        for run in 0..RUNS {
831            let signal = Signal::new(0_u64).unwrap();
832            let barrier = Arc::new(Barrier::new(2));
833            let writer_signal = signal.clone();
834            let writer_barrier = Arc::clone(&barrier);
835            let writer = thread::spawn(move || {
836                writer_barrier.wait();
837                writer_signal.set((run + 1) as u64).unwrap();
838            });
839
840            barrier.wait();
841            let observed = signal.changes().take(2).run_with(Sink::collect()).unwrap();
842            writer.join().unwrap();
843            signal.close().unwrap();
844            let values = wait(observed);
845            assert!(!values.is_empty());
846            let expected = (run + 1) as u64;
847            assert_eq!(*signal.get(), expected);
848            assert!(
849                values.contains(&expected),
850                "subscription missed concurrent set: {values:?}"
851            );
852        }
853    }
854
855    #[test]
856    fn signal_coalesces_slow_subscriber_but_observes_final() {
857        const WRITES: u64 = 512;
858        let signal = Signal::new(0_u64).unwrap();
859        let seen = Arc::new(Mutex::new(Vec::new()));
860        let sink_seen = Arc::clone(&seen);
861        let gate = Arc::new(AtomicBool::new(false));
862        let sink_gate = Arc::clone(&gate);
863
864        let completion = signal
865            .changes()
866            .run_with(Sink::foreach(move |item| {
867                sink_seen.lock().unwrap().push(item);
868                while !sink_gate.load(Ordering::SeqCst) {
869                    thread::yield_now();
870                }
871            }))
872            .unwrap();
873
874        assert!(wait_until(Duration::from_secs(1), || {
875            !seen.lock().unwrap().is_empty()
876        }));
877
878        for value in 1..=WRITES {
879            signal.set(value).unwrap();
880        }
881        signal.close().unwrap();
882        gate.store(true, Ordering::SeqCst);
883        wait(completion);
884
885        let values = seen.lock().unwrap().clone();
886        assert!(values.len() < WRITES as usize);
887        assert_eq!(values.last().copied(), Some(WRITES));
888    }
889
890    #[test]
891    fn post_close_subscribe_yields_final_then_complete() {
892        let signal = Signal::new(1_u64).unwrap();
893        signal.close_with(9).unwrap();
894        let values = signal.changes().run_collect().unwrap();
895        assert_eq!(values, vec![9]);
896    }
897
898    #[test]
899    fn benchmark_stream_sees_seed() {
900        let signal = Signal::new(7_u64).unwrap();
901        let runtime = tokio::runtime::Runtime::new().unwrap();
902        let mut stream = signal.__benchmark_changes().unwrap();
903        let seed = runtime
904            .block_on(stream.next())
905            .expect("benchmark stream ended before seed")
906            .expect("benchmark stream failed before seed");
907        assert_eq!(seed, 7);
908    }
909
910    #[test]
911    fn benchmark_stream_sees_final_after_writes() {
912        let signal = Signal::new(0_u64).unwrap();
913        let runtime = tokio::runtime::Runtime::new().unwrap();
914        let mut stream = signal.__benchmark_changes().unwrap();
915        let seed = runtime
916            .block_on(stream.next())
917            .expect("benchmark stream ended before seed")
918            .expect("benchmark stream failed before seed");
919        assert_eq!(seed, 0);
920        for value in 1..=16 {
921            signal.set_eventually(value).unwrap();
922        }
923        let final_value = runtime.block_on(async {
924            loop {
925                let value = stream
926                    .next()
927                    .await
928                    .expect("benchmark stream ended before final")
929                    .expect("benchmark stream failed before final");
930                if value >= 16 {
931                    break value;
932                }
933            }
934        });
935        assert_eq!(final_value, 16);
936    }
937
938    #[test]
939    fn benchmark_spawned_stream_sees_final_after_ready() {
940        let signal = Signal::new(0_u64).unwrap();
941        let runtime = tokio::runtime::Runtime::new().unwrap();
942        let mut stream = signal.__benchmark_changes().unwrap();
943        let ready = Arc::new(AtomicBool::new(false));
944        let task_ready = Arc::clone(&ready);
945        let handle = runtime.spawn(async move {
946            let seed = stream
947                .next()
948                .await
949                .expect("benchmark stream ended before seed")
950                .expect("benchmark stream failed before seed");
951            assert_eq!(seed, 0);
952            task_ready.store(true, Ordering::Release);
953            loop {
954                let value = stream
955                    .next()
956                    .await
957                    .expect("benchmark stream ended before final")
958                    .expect("benchmark stream failed before final");
959                if value >= 1024 {
960                    return value;
961                }
962            }
963        });
964        runtime.block_on(async {
965            while !ready.load(Ordering::Acquire) {
966                tokio::task::yield_now().await;
967            }
968        });
969        for value in 1..=1024 {
970            signal.set_eventually(value).unwrap();
971        }
972        let final_value = runtime
973            .block_on(async { tokio::time::timeout(Duration::from_secs(1), handle).await })
974            .expect("spawned signal subscriber timed out")
975            .expect("spawned signal subscriber panicked");
976        assert_eq!(final_value, 1024);
977    }
978
979    #[test]
980    fn dropping_feed_source_cancels_cleanly() {
981        let signal = Signal::new(0_u64).unwrap();
982        let pulled = Arc::new(AtomicUsize::new(0));
983        let sink_pulled = Arc::clone(&pulled);
984        let completion = signal
985            .changes()
986            .run_with(Sink::foreach(move |_| {
987                sink_pulled.fetch_add(1, Ordering::SeqCst);
988            }))
989            .unwrap();
990        assert!(wait_until(Duration::from_secs(1), || {
991            pulled.load(Ordering::SeqCst) == 1
992        }));
993        drop(completion);
994        assert!(wait_until(Duration::from_secs(1), || signal.set(1).is_ok()));
995    }
996
997    #[test]
998    fn actor_death_fails_feed() {
999        let signal = Signal::new(0_u64).unwrap();
1000        let materializer = Materializer::new();
1001        let completion = signal
1002            .changes()
1003            .drop(1)
1004            .run_with_materializer(Sink::head(), &materializer)
1005            .unwrap();
1006        drop(signal);
1007        match completion.wait() {
1008            Err(StreamError::ActorTerminated) => {}
1009            other => panic!("expected actor termination, got {other:?}"),
1010        }
1011    }
1012}