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
36pub 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 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 #[must_use]
123 pub fn get(&self) -> Arc<T> {
124 self.inner.shared.mirror.load_full()
125 }
126
127 #[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 pub fn set(&self, value: T) -> StreamResult<()> {
142 self.publish_set(Arc::new(value))
143 }
144
145 pub fn set_eventually(&self, value: T) -> StreamResult<()> {
150 self.publish_set(Arc::new(value))
151 }
152
153 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 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 pub fn close(&self) -> StreamResult<()> {
176 self.send_close(None)
177 }
178
179 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 #[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}