klukai_types/tripwire/
mod.rs1#![warn(missing_docs)]
5#![deny(clippy::await_holding_lock)]
6
7mod preempt;
8mod signalstream;
9
10pub use preempt::{Outcome, PreemptibleFuture, PreemptibleFutureExt, TimeoutFutureExt};
11
12use futures::stream::Stream;
13use futures_util::stream::{Select, select};
14use std::{
15 future::Future,
16 pin::Pin,
17 task::{Context, Poll},
18};
19use tokio::{
20 signal::unix::{SignalKind, signal},
21 sync::{mpsc, watch},
22};
23use tokio_stream::wrappers::{ReceiverStream, WatchStream};
24use tracing::{debug, warn};
25
26use crate::tripwire::signalstream::SignalStream;
27
28#[derive(Debug)]
32pub struct Tripwire {
33 subscription: WatchStream<TripwireState>,
34 subscription_rx: watch::Receiver<TripwireState>,
35}
36
37impl Tripwire {
38 pub fn new<S>(stream: S) -> (Self, TripwireWorker<S>)
41 where
42 S: Stream<Item = ()>,
43 {
44 let (sender, receiver) = watch::channel(TripwireState::Running);
45 let tripwire = Tripwire {
46 subscription: WatchStream::new(receiver.clone()),
47 subscription_rx: receiver,
48 };
49 let worker = TripwireWorker {
50 subscription: sender,
51 stream,
52 };
53 (tripwire, worker)
54 }
55
56 pub fn new_simple() -> (Self, TripwireWorker<ReceiverStream<()>>, mpsc::Sender<()>) {
59 let (tx, rx) = mpsc::channel::<()>(1);
60 let (tw, w) = Self::new(ReceiverStream::new(rx));
61 (tw, w, tx)
62 }
63
64 pub fn new_signals() -> (Self, TripwireWorker<Select<SignalStream, SignalStream>>) {
66 let sigterms = SignalStream::new(signal(SignalKind::terminate()).unwrap());
67 let sigints = SignalStream::new(signal(SignalKind::interrupt()).unwrap());
68 Self::new(select(sigterms, sigints))
69 }
70
71 pub fn state(&self) -> TripwireState {
73 *self.subscription_rx.borrow()
74 }
75
76 pub fn is_shutting_down(&self) -> bool {
78 matches!(self.state(), TripwireState::ShuttingDown)
79 }
80
81 pub fn tripwired<F>(self, inner: F) -> Tripwired<F> {
83 Tripwired {
84 tripwire: self,
85 inner,
86 }
87 }
88}
89
90impl Clone for Tripwire {
91 fn clone(&self) -> Self {
92 Tripwire {
93 subscription: WatchStream::new(self.subscription_rx.clone()),
94 subscription_rx: self.subscription_rx.clone(),
95 }
96 }
97}
98
99impl Future for Tripwire {
100 type Output = ();
101
102 fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
103 loop {
104 match futures::ready!(Pin::new(&mut self.subscription).poll_next(cx)) {
105 Some(TripwireState::Running) => {}
106 Some(TripwireState::ShuttingDown) => return Poll::Ready(()),
107 None => return Poll::Ready(()),
108 }
109 }
110 }
111}
112
113pub struct Tripwired<F> {
115 tripwire: Tripwire,
116 inner: F,
117}
118
119impl<F> Future for Tripwired<F>
120where
121 F: Future<Output = ()> + Unpin,
122{
123 type Output = F::Output;
124
125 fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
126 if let Poll::Ready(()) = Pin::new(&mut self.tripwire).poll(cx) {
127 return Poll::Ready(());
128 }
129
130 Pin::new(&mut self.inner).poll(cx)
131 }
132}
133
134impl<S> Stream for Tripwired<S>
135where
136 S: Stream + Unpin,
137{
138 type Item = S::Item;
139
140 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
141 if let Poll::Ready(()) = Pin::new(&mut self.tripwire).poll(cx) {
142 return Poll::Ready(None);
143 }
144
145 Pin::new(&mut self.inner).poll_next(cx)
146 }
147}
148
149#[derive(Copy, Clone, Debug)]
151#[allow(missing_docs)]
152pub enum TripwireState {
153 Running,
154 ShuttingDown,
155}
156
157pub struct TripwireWorker<S> {
160 subscription: watch::Sender<TripwireState>,
161 stream: S,
162}
163
164impl<S> Future for TripwireWorker<S>
165where
166 S: Stream + Unpin,
167{
168 type Output = ();
169
170 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
171 match futures::ready!(Pin::new(&mut self.stream).poll_next(cx)) {
172 Some(_) => "either SIGTERM or SIGINT",
173 None => return Poll::Ready(()),
174 };
175
176 println!(); debug!("TripwireWorker tripped");
178
179 if let Err(error) = self.subscription.send(TripwireState::ShuttingDown) {
180 warn!("all subscription handles have been cancelled?: {:?}", error);
181 }
182 debug!("tripwire worker's subscription has been updated");
183 Poll::Ready(())
184 }
185}