Skip to content

Commit c4d250b

Browse files
committed
chore: Merge branch 'main' into feat/webhook-improve-instrumentation
2 parents fd256cf + f2a0840 commit c4d250b

3 files changed

Lines changed: 78 additions & 0 deletions

File tree

crates/stackable-operator/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ All notable changes to this project will be documented in this file.
88

99
- BREAKING: Add support to gracefully shutdown `EosChecker`.
1010
`EosChecker::run` now requires passing a shutdown signal, which is any `Future<Output = ()>` ([#1146]).
11+
- Add `SignalWatcher` which can be used to watch signals and multiply them to gracefully shutdown
12+
multiple concurrent tasks/futures ([#1147]).
1113

1214
[#1146]: https://github.com/stackabletech/operator-rs/pull/1146
15+
[#1147]: https://github.com/stackabletech/operator-rs/pull/1147
1316

1417
## [0.104.0] - 2026-01-26
1518

crates/stackable-operator/src/utils/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ pub mod cluster_info;
33
pub mod crds;
44
pub mod kubelet;
55
pub mod logging;
6+
pub mod signal;
7+
68
mod option;
79
mod url;
810

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use snafu::{ResultExt, Snafu};
2+
use tokio::{
3+
signal::unix::{SignalKind, signal},
4+
sync::watch,
5+
};
6+
7+
#[derive(Debug, Snafu)]
8+
#[snafu(display("failed to construct signal watcher"))]
9+
pub struct SignalError {
10+
source: std::io::Error,
11+
}
12+
13+
/// Watches for the incoming signal and multiplies it by sending it to all acquired handles.
14+
pub struct SignalWatcher<T>
15+
where
16+
T: Send + Sync + 'static,
17+
{
18+
watch_rx: watch::Receiver<T>,
19+
}
20+
21+
impl<T> SignalWatcher<T>
22+
where
23+
T: Default + Send + Sync + 'static,
24+
{
25+
/// Watches the provided `signal` and multiplies the signal by sending it to all acquired handles
26+
/// constructed through [`SignalWatcher::handle`].
27+
pub fn new<F>(signal: F) -> Self
28+
where
29+
F: Future<Output = T> + Send + Sync + 'static,
30+
{
31+
let (watch_tx, watch_rx) = watch::channel(T::default());
32+
33+
tokio::spawn(async move {
34+
let value = signal.await;
35+
watch_tx.send(value)
36+
});
37+
38+
Self { watch_rx }
39+
}
40+
41+
/// Acquire a new handle which will complete once a `SIGTERM` signal is received.
42+
///
43+
/// This handle can be cheaply cloned to be able to gracefully shutdown multiple concurrent
44+
/// tasks.
45+
pub fn handle(&self) -> impl Future<Output = ()> + use<T> {
46+
let mut watch_rx = self.watch_rx.clone();
47+
48+
async move {
49+
watch_rx.changed().await.ok();
50+
}
51+
}
52+
}
53+
54+
impl SignalWatcher<()> {
55+
/// Watches the `SIGTERM` signal and multiplies the signal by sending it to all acquired handlers
56+
/// constructed through [`SignalWatcher::handle`].
57+
//
58+
// NOTE (@Techassi): Note Accepting a generic Future<Output = ()> here is possible, but
59+
// `Signal::recv` borrows instead of consuming which clashes with the 'static lifetime
60+
// requirement of `tokio::spawn`. That's why I opted for watching for a particular signal
61+
// internally instead of requiring users to pass the signal to this function.
62+
pub fn sigterm() -> Result<Self, SignalError> {
63+
let mut sigterm = signal(SignalKind::terminate()).context(SignalSnafu)?;
64+
let (watch_tx, watch_rx) = watch::channel(());
65+
66+
tokio::spawn(async move {
67+
sigterm.recv().await;
68+
watch_tx.send(())
69+
});
70+
71+
Ok(Self { watch_rx })
72+
}
73+
}

0 commit comments

Comments
 (0)