proxima_primitives/pipe/
signal_source.rs1use core::future::Future;
11use core::pin::Pin;
12use core::sync::atomic::{AtomicBool, Ordering};
13use core::task::{Context, Poll};
14
15use proxima_core::markers::DropSafe;
16use proxima_core::signal::{Fired, Signal};
17
18use crate::pipe::fan_in::Exhausted;
19use crate::pipe::primitives::UnpinPipe;
20
21pub struct SignalSource {
24 fired: Fired,
25 yielded: AtomicBool,
26}
27
28impl SignalSource {
29 #[must_use]
30 pub fn new(signal: &Signal) -> Self {
31 Self {
32 fired: signal.fired(),
33 yielded: AtomicBool::new(false),
34 }
35 }
36}
37
38impl DropSafe for SignalSource {}
39
40enum SignalCall<'source> {
45 Yielded,
46 Pending { source: &'source SignalSource },
47}
48
49impl Future for SignalCall<'_> {
50 type Output = Result<(), Exhausted>;
51
52 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
53 match self.get_mut() {
54 SignalCall::Yielded => Poll::Ready(Err(Exhausted)),
55 SignalCall::Pending { source } => match source.fired.poll_shared(cx) {
56 Poll::Ready(()) => {
57 source.yielded.store(true, Ordering::Relaxed);
58 Poll::Ready(Ok(()))
59 }
60 Poll::Pending => Poll::Pending,
61 },
62 }
63 }
64}
65
66impl SignalSource {
67 fn call_future(&self) -> SignalCall<'_> {
68 if self.yielded.load(Ordering::Relaxed) {
69 SignalCall::Yielded
70 } else {
71 SignalCall::Pending { source: self }
72 }
73 }
74}
75
76impl UnpinPipe for SignalSource {
77 type In = ();
78 type Out = ();
79 type Err = Exhausted;
80
81 fn call(&self, (): ()) -> impl Future<Output = Result<(), Exhausted>> + Unpin {
82 self.call_future()
83 }
84}
85
86#[cfg(test)]
87#[allow(clippy::unwrap_used, clippy::expect_used)]
88mod tests {
89 use std::sync::Arc;
90 use std::sync::atomic::AtomicUsize;
91 use std::task::{Wake, Waker};
92
93 use super::*;
94 use crate::pipe::fan_in::{FanIn, Select};
95 use crate::pipe::primitives::Pipe;
96
97 fn noop_context() -> Context<'static> {
98 Context::from_waker(std::task::Waker::noop())
99 }
100
101 fn call_once(source: &SignalSource, cx: &mut Context<'_>) -> Poll<Result<(), Exhausted>> {
102 let mut call = source.call(());
103 Pin::new(&mut call).poll(cx)
104 }
105
106
107 struct CountingWaker(AtomicUsize);
108
109 impl Wake for CountingWaker {
110 fn wake(self: Arc<Self>) {
111 self.0.fetch_add(1, Ordering::Relaxed);
112 }
113 }
114
115 fn counting_waker() -> (Arc<CountingWaker>, Waker) {
116 let counter = Arc::new(CountingWaker(AtomicUsize::new(0)));
117 let waker = Waker::from(counter.clone());
118 (counter, waker)
119 }
120
121 enum Arm {
122 Cancel(SignalSource),
123 Idle,
124 }
125
126 impl DropSafe for Arm {}
127
128 enum ArmCall<'arm> {
129 Cancel(SignalCall<'arm>),
130 Idle,
131 }
132
133 impl Future for ArmCall<'_> {
134 type Output = Result<(), Exhausted>;
135
136 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
137 match self.get_mut() {
138 ArmCall::Cancel(call) => Pin::new(call).poll(cx),
139 ArmCall::Idle => Poll::Pending,
140 }
141 }
142 }
143
144 impl UnpinPipe for Arm {
145 type In = ();
146 type Out = ();
147 type Err = Exhausted;
148
149 fn call(&self, (): ()) -> impl Future<Output = Result<(), Exhausted>> + Unpin {
150 match self {
151 Arm::Cancel(source) => ArmCall::Cancel(source.call_future()),
152 Arm::Idle => ArmCall::Idle,
153 }
154 }
155 }
156
157 fn merged(signal: &Signal) -> FanIn<Arm, Select, 2> {
158 FanIn::new(
159 [Arm::Cancel(SignalSource::new(signal)), Arm::Idle],
160 Select::RoundRobin,
161 )
162 }
163
164 fn poll_merge(
165 merge: &FanIn<Arm, Select, 2>,
166 cx: &mut Context<'_>,
167 ) -> Poll<Result<(), Exhausted>> {
168 let mut call = Pipe::call(merge, ());
169 Pin::new(&mut call).poll(cx)
170 }
171
172 #[test]
173 fn fan_in_parked_on_a_signal_source_is_woken_by_fire() {
174 let signal = Signal::new();
175 let merge = merged(&signal);
176 let (counter, waker) = counting_waker();
177 let mut cx = Context::from_waker(&waker);
178 assert_eq!(poll_merge(&merge, &mut cx), Poll::Pending);
179
180 signal.fire();
181
182 assert!(counter.0.load(Ordering::Relaxed) >= 1, "fire must wake the parked fan-in");
183 assert_eq!(poll_merge(&merge, &mut cx), Poll::Ready(Ok(())));
184 }
185
186 #[test]
187 fn dropping_the_source_unregisters_its_waker() {
188 let signal = Signal::new();
189 let (counter, waker) = counting_waker();
190 let mut cx = Context::from_waker(&waker);
191 let source = SignalSource::new(&signal);
192 assert_eq!(call_once(&source, &mut cx), Poll::Pending);
193
194 drop(source);
195 signal.fire();
196
197 assert_eq!(counter.0.load(Ordering::Relaxed), 0);
198 }
199
200 #[test]
201 fn fan_in_over_an_unfired_signal_source_is_never_woken() {
202 let signal = Signal::new();
203 let merge = merged(&signal);
204 let (counter, waker) = counting_waker();
205 let mut cx = Context::from_waker(&waker);
206 for _ in 0..4 {
207 assert_eq!(poll_merge(&merge, &mut cx), Poll::Pending);
208 }
209 assert_eq!(counter.0.load(Ordering::Relaxed), 0);
210 }
211 #[test]
212 fn yields_unit_once_when_fired_then_exhausts() {
213 let signal = Signal::new();
214 let source = SignalSource::new(&signal);
215 let mut cx = noop_context();
216 assert_eq!(call_once(&source, &mut cx), Poll::Pending);
217 signal.fire();
218 assert_eq!(call_once(&source, &mut cx), Poll::Ready(Ok(())));
219 assert_eq!(call_once(&source, &mut cx), Poll::Ready(Err(Exhausted)));
220 }
221
222 #[test]
223 fn late_subscriber_yields_immediately() {
224 let signal = Signal::new();
225 signal.fire();
226 let source = SignalSource::new(&signal);
227 let mut cx = noop_context();
228 assert_eq!(call_once(&source, &mut cx), Poll::Ready(Ok(())));
229 }
230}