saluki_common/sync/
shutdown.rs1use std::{
4 future::Future,
5 pin::Pin,
6 sync::{
7 atomic::{
8 AtomicBool, AtomicUsize,
9 Ordering::{AcqRel, Acquire, Release},
10 },
11 Arc,
12 },
13 task::{Context, Poll},
14};
15
16use pin_project::{pin_project, pinned_drop};
17use tokio::sync::{futures::OwnedNotified, Notify};
18
19#[derive(Default)]
20struct ShutdownInner {
21 flag: AtomicBool,
22 notify: Arc<Notify>,
23 outstanding: AtomicUsize,
24 all_dropped: Notify,
25}
26
27impl ShutdownInner {
28 fn register(&self) {
30 self.outstanding.fetch_add(1, Release);
31 }
32
33 fn deregister(&self) {
35 if self.outstanding.fetch_sub(1, AcqRel) == 1 {
36 self.all_dropped.notify_waiters();
39 }
40 }
41
42 fn signal(&self) {
44 if !self.flag.swap(true, AcqRel) {
45 self.notify.notify_waiters();
46 }
47 }
48
49 async fn signal_and_await_handles(&self) {
53 let all_dropped = self.all_dropped.notified();
56
57 self.signal();
58
59 if self.outstanding.load(Acquire) == 0 {
60 return;
61 }
62
63 all_dropped.await;
64 }
65}
66
67#[derive(Default)]
83pub struct ShutdownCoordinator {
84 state: Arc<ShutdownInner>,
85}
86
87impl ShutdownCoordinator {
88 pub fn register(&mut self) -> ShutdownHandle {
90 ShutdownHandle::registered(Arc::clone(&self.state))
91 }
92
93 pub fn shutdown(self) {
98 self.state.signal();
99 }
100
101 pub async fn shutdown_and_wait(self) {
105 self.state.signal_and_await_handles().await;
106 }
107}
108
109impl Drop for ShutdownCoordinator {
110 fn drop(&mut self) {
111 self.state.signal();
112 }
113}
114
115#[pin_project(PinnedDrop)]
121pub struct ShutdownHandle {
122 state: Arc<ShutdownInner>,
123
124 #[pin]
125 notified: OwnedNotified,
126}
127
128impl ShutdownHandle {
129 fn registered(state: Arc<ShutdownInner>) -> Self {
131 state.register();
132
133 let notify = Arc::clone(&state.notify);
138 let notified = notify.notified_owned();
139
140 Self { state, notified }
141 }
142
143 pub fn noop() -> Self {
147 Self::registered(Arc::new(ShutdownInner::default()))
148 }
149
150 pub fn paired() -> (ShutdownCoordinator, Self) {
155 let mut coordinator = ShutdownCoordinator::default();
156 let handle = coordinator.register();
157 (coordinator, handle)
158 }
159
160 pub fn is_triggered(&self) -> bool {
162 self.state.flag.load(Acquire)
163 }
164
165 pub fn view(&self) -> ShutdownView {
169 ShutdownView {
170 state: Arc::clone(&self.state),
171 }
172 }
173}
174
175#[derive(Clone)]
180pub struct ShutdownView {
181 state: Arc<ShutdownInner>,
182}
183
184impl ShutdownView {
185 pub fn is_triggered(&self) -> bool {
187 self.state.flag.load(Acquire)
188 }
189}
190
191impl Future for ShutdownHandle {
192 type Output = ();
193
194 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
195 let this = self.project();
196
197 if this.state.flag.load(Acquire) {
199 return Poll::Ready(());
200 }
201
202 this.notified.poll(cx)
208 }
209}
210
211#[pinned_drop]
212impl PinnedDrop for ShutdownHandle {
213 fn drop(self: Pin<&mut Self>) {
214 self.state.deregister();
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use tokio_test::{assert_pending, assert_ready, task::spawn};
221
222 use super::*;
223
224 #[test]
225 fn handle_starts_untriggered() {
226 let (_coordinator, handle) = ShutdownHandle::paired();
227
228 assert!(!handle.is_triggered());
229 }
230
231 #[test]
232 fn handle_triggered_on_coordinator_shutdown() {
233 let (coordinator, handle) = ShutdownHandle::paired();
234 assert!(!handle.is_triggered());
235
236 coordinator.shutdown();
237 assert!(handle.is_triggered());
238 }
239
240 #[test]
241 fn handle_triggered_on_coordinator_drop() {
242 let (coordinator, handle) = ShutdownHandle::paired();
243 assert!(!handle.is_triggered());
244
245 drop(coordinator);
246 assert!(handle.is_triggered());
247 }
248
249 #[test]
250 fn view_reports_the_state_of_its_handle() {
251 let (coordinator, handle) = ShutdownHandle::paired();
252 let view = handle.view();
253 assert!(!view.is_triggered());
254
255 coordinator.shutdown();
256 assert!(view.is_triggered());
257 }
258
259 #[tokio::test]
260 async fn view_is_not_waited_for() {
261 let (coordinator, handle) = ShutdownHandle::paired();
262 let view = handle.view();
263
264 drop(handle);
267 tokio::time::timeout(std::time::Duration::from_secs(1), coordinator.shutdown_and_wait())
268 .await
269 .expect("the view must not hold up the coordinator");
270 assert!(view.is_triggered());
271 }
272
273 #[test]
274 fn handle_wait_returns_immediately_when_already_triggered() {
275 let (coordinator, handle) = ShutdownHandle::paired();
276 coordinator.shutdown();
277
278 let mut wait = spawn(handle);
279 assert_ready!(wait.poll());
280 }
281
282 #[test]
283 fn handle_completes_after_coordinator_shutdown() {
284 let (coordinator, handle) = ShutdownHandle::paired();
285
286 let mut wait = spawn(handle);
287 assert_pending!(wait.poll());
288 assert!(!wait.is_woken());
289
290 coordinator.shutdown();
292 assert!(wait.is_woken());
293 assert_ready!(wait.poll());
294 }
295
296 #[test]
297 fn handle_completes_after_coordinator_dropped() {
298 let (coordinator, handle) = ShutdownHandle::paired();
299
300 let mut wait = spawn(handle);
301 assert_pending!(wait.poll());
302 assert!(!wait.is_woken());
303
304 drop(coordinator);
307 assert!(wait.is_woken());
308 assert_ready!(wait.poll());
309 }
310
311 #[test]
312 fn handle_is_fused_after_coordinator_shutdown() {
313 let (coordinator, handle) = ShutdownHandle::paired();
314 coordinator.shutdown();
315
316 let mut wait = spawn(handle);
318 assert_ready!(wait.poll());
319 assert_ready!(wait.poll());
320 }
321
322 #[test]
323 fn multiple_handles_all_wake_on_shutdown() {
324 let mut coordinator = ShutdownCoordinator::default();
325 let handle1 = coordinator.register();
326 let handle2 = coordinator.register();
327 let handle3 = coordinator.register();
328
329 let mut wait1 = spawn(handle1);
330 let mut wait2 = spawn(handle2);
331 let mut wait3 = spawn(handle3);
332
333 assert_pending!(wait1.poll());
334 assert_pending!(wait2.poll());
335 assert_pending!(wait3.poll());
336
337 coordinator.shutdown();
339 assert!(wait1.is_woken());
340 assert!(wait2.is_woken());
341 assert!(wait3.is_woken());
342
343 assert_ready!(wait1.poll());
344 assert_ready!(wait2.poll());
345 assert_ready!(wait3.poll());
346 }
347
348 #[test]
349 fn noop_handle_never_completes() {
350 let handle = ShutdownHandle::noop();
351
352 assert!(!handle.is_triggered());
354
355 let mut wait = spawn(handle);
356 assert_pending!(wait.poll());
357 assert_pending!(wait.poll());
358 assert!(!wait.is_woken());
359 }
360
361 #[test]
362 fn coordinator_shutdown_and_wait_returns_immediately_with_no_handles() {
363 let coordinator = ShutdownCoordinator::default();
364
365 let mut shutdown = spawn(coordinator.shutdown_and_wait());
367 assert_ready!(shutdown.poll());
368 }
369
370 #[test]
371 fn coordinator_shutdown_and_wait_ignores_handle_dropped_before_shutdown() {
372 let mut coordinator = ShutdownCoordinator::default();
373
374 let handle = coordinator.register();
377 drop(handle);
378
379 let mut shutdown = spawn(coordinator.shutdown_and_wait());
381 assert_ready!(shutdown.poll());
382 }
383
384 #[test]
385 fn coordinator_shutdown_waits_for_outstanding_handle_to_drop_single() {
386 let mut coordinator = ShutdownCoordinator::default();
387 let handle = coordinator.register();
388
389 let mut wait = spawn(handle);
390 assert!(!wait.is_woken());
391 assert_pending!(wait.poll());
392
393 let mut shutdown = spawn(coordinator.shutdown_and_wait());
395 assert!(!shutdown.is_woken());
396 assert_pending!(shutdown.poll());
397 assert!(!shutdown.is_woken());
398
399 assert!(wait.is_woken());
402 assert_ready!(wait.poll());
403
404 assert_pending!(shutdown.poll());
405 assert!(!shutdown.is_woken());
406
407 drop(wait);
408
409 assert!(shutdown.is_woken());
410 assert_ready!(shutdown.poll());
411 }
412
413 #[test]
414 fn coordinator_shutdown_waits_for_outstanding_handle_to_drop_multiple() {
415 let mut coordinator = ShutdownCoordinator::default();
416 let handle1 = coordinator.register();
417 let handle2 = coordinator.register();
418
419 let mut wait1 = spawn(handle1);
420 let mut wait2 = spawn(handle2);
421
422 assert!(!wait1.is_woken());
423 assert!(!wait2.is_woken());
424 assert_pending!(wait1.poll());
425 assert_pending!(wait2.poll());
426
427 let mut shutdown = spawn(coordinator.shutdown_and_wait());
429 assert!(!shutdown.is_woken());
430 assert_pending!(shutdown.poll());
431 assert!(!shutdown.is_woken());
432
433 assert!(wait1.is_woken());
436 assert_ready!(wait1.poll());
437
438 assert!(wait2.is_woken());
439 assert_ready!(wait2.poll());
440
441 assert!(!shutdown.is_woken());
442 assert_pending!(shutdown.poll());
443
444 drop(wait1);
446
447 assert!(!shutdown.is_woken());
448 assert_pending!(shutdown.poll());
449
450 drop(wait2);
452
453 assert!(shutdown.is_woken());
454 assert_ready!(shutdown.poll());
455 }
456}