kube_runtime/utils/
mod.rs1mod backoff_reset_timer;
4pub(crate) mod delayed_init;
5mod event_decode;
6mod event_modify;
7mod predicate;
8mod reflect;
9mod stream_backoff;
10mod watch_ext;
11
12pub use backoff_reset_timer::{Backoff, ResetTimerBackoff};
13pub use event_decode::EventDecode;
14pub use event_modify::EventModify;
15pub use predicate::{Config as PredicateConfig, Predicate, PredicateFilter, predicates};
16pub use reflect::Reflect;
17pub use stream_backoff::StreamBackoff;
18pub use watch_ext::WatchStreamExt;
19
20use futures::{
21 FutureExt, Stream, StreamExt, TryStream, TryStreamExt,
22 stream::{self, Peekable},
23};
24use pin_project::pin_project;
25use std::{
26 fmt::Debug,
27 pin::{Pin, pin},
28 sync::{Arc, Mutex},
29 task::Poll,
30};
31use stream::IntoStream;
32use tokio::{runtime::Handle, task::JoinHandle};
33
34#[pin_project]
42pub(crate) struct SplitCase<S: Stream, Case> {
43 inner: Arc<Mutex<Peekable<S>>>,
45 should_consume_item: fn(&S::Item) -> bool,
50 try_extract_item_case: fn(S::Item) -> Option<Case>,
55}
56
57impl<S, Case> Stream for SplitCase<S, Case>
58where
59 S: Stream + Unpin,
60 S::Item: Debug,
61{
62 type Item = Case;
63
64 fn poll_next(
65 self: std::pin::Pin<&mut Self>,
66 cx: &mut std::task::Context<'_>,
67 ) -> std::task::Poll<Option<Self::Item>> {
68 let this = self.project();
69 let inner = this.inner.lock().unwrap();
70 let mut inner = Pin::new(inner);
71 let inner_peek = pin!(inner.as_mut().peek());
72 match inner_peek.poll(cx) {
73 Poll::Ready(Some(x_ref)) => {
74 if (this.should_consume_item)(x_ref) {
75 let item = inner.as_mut().poll_next(cx);
76 match item {
77 Poll::Ready(Some(x)) => Poll::Ready(Some((this.try_extract_item_case)(x).expect(
78 "`try_extract_item_case` returned `None` despite `should_consume_item` returning `true`",
79 ))),
80 res => panic!(
81 "Peekable::poll_next() returned {res:?} when Peekable::peek() returned Ready(Some(_))"
82 ),
83 }
84 } else {
85 Poll::Pending
87 }
88 }
89 Poll::Ready(None) => Poll::Ready(None),
90 Poll::Pending => Poll::Pending,
91 }
92 }
93}
94
95#[allow(clippy::type_complexity)]
99fn trystream_split_result<S>(
100 stream: S,
101) -> (
102 SplitCase<IntoStream<S>, S::Ok>,
103 SplitCase<IntoStream<S>, S::Error>,
104)
105where
106 S: TryStream + Unpin,
107 S::Ok: Debug,
108 S::Error: Debug,
109{
110 let stream = Arc::new(Mutex::new(stream.into_stream().peekable()));
111 (
112 SplitCase {
113 inner: stream.clone(),
114 should_consume_item: Result::is_ok,
115 try_extract_item_case: Result::ok,
116 },
117 SplitCase {
118 inner: stream,
119 should_consume_item: Result::is_err,
120 try_extract_item_case: Result::err,
121 },
122 )
123}
124
125pub(crate) fn trystream_try_via<S1, S2>(
127 input_stream: S1,
128 make_via_stream: impl FnOnce(SplitCase<IntoStream<S1>, S1::Ok>) -> S2,
129) -> impl Stream<Item = Result<S2::Ok, S1::Error>>
130where
131 S1: TryStream + Unpin,
132 S2: TryStream<Error = S1::Error>,
133 S1::Ok: Debug,
134 S1::Error: Debug,
135{
136 let (oks, errs) = trystream_split_result(input_stream); let via = make_via_stream(oks); stream::select(via.into_stream(), errs.map(Err)) }
140
141pub struct CancelableJoinHandle<T> {
143 inner: JoinHandle<T>,
144}
145
146impl<T> CancelableJoinHandle<T>
147where
148 T: Send + 'static,
149{
150 pub fn spawn(future: impl Future<Output = T> + Send + 'static, runtime: &Handle) -> Self {
152 CancelableJoinHandle {
153 inner: runtime.spawn(future),
154 }
155 }
156}
157
158impl<T> Drop for CancelableJoinHandle<T> {
159 fn drop(&mut self) {
160 self.inner.abort()
161 }
162}
163
164impl<T> Future for CancelableJoinHandle<T> {
165 type Output = T;
166
167 fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
168 self.inner.poll_unpin(cx).map(
169 Result::unwrap,
172 )
173 }
174}
175
176#[pin_project]
177pub(crate) struct OnComplete<S, F> {
178 #[pin]
179 stream: stream::Fuse<S>,
180 #[pin]
181 on_complete: F,
182}
183
184impl<S: Stream, F: Future<Output = ()>> Stream for OnComplete<S, F> {
185 type Item = S::Item;
186
187 fn poll_next(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Option<Self::Item>> {
188 let this = self.project();
189 match this.stream.poll_next(cx) {
190 Poll::Ready(None) => match this.on_complete.poll(cx) {
191 Poll::Pending => Poll::Pending,
192 Poll::Ready(()) => Poll::Ready(None),
193 },
194 x => x,
195 }
196 }
197}
198
199pub(crate) trait KubeRuntimeStreamExt: Stream + Sized {
200 fn on_complete<F: Future<Output = ()>>(self, on_complete: F) -> OnComplete<Self, F> {
202 OnComplete {
203 stream: self.fuse(),
204 on_complete,
205 }
206 }
207}
208
209impl<S: Stream> KubeRuntimeStreamExt for S {}
210
211#[cfg(test)]
212mod tests {
213 use std::convert::Infallible;
214
215 use futures::stream::{self, StreamExt};
216
217 use super::trystream_try_via;
218
219 #[allow(dead_code)]
221 fn trystream_try_via_should_be_able_to_borrow() {
222 struct WeirdComplexObject {}
223 impl Drop for WeirdComplexObject {
224 fn drop(&mut self) {}
225 }
226
227 let mut x = WeirdComplexObject {};
228 let y = WeirdComplexObject {};
229 drop(trystream_try_via(
230 Box::pin(stream::once(async {
231 let _ = &mut x;
232 Result::<_, Infallible>::Ok(())
233 })),
234 |s| {
235 s.map(|()| {
236 let _ = &y;
237 Ok(())
238 })
239 },
240 ));
241 }
242}