From 48899b5ca42cd95d4ce47dc7471cf0b521ea550b Mon Sep 17 00:00:00 2001 From: Techassi Date: Fri, 30 Jan 2026 13:38:12 +0100 Subject: [PATCH 1/3] feat(operator): Add graceful shutdown to EoS checker --- crates/stackable-operator/src/eos/mod.rs | 56 ++++++++++++++++-------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/crates/stackable-operator/src/eos/mod.rs b/crates/stackable-operator/src/eos/mod.rs index 243cb365a..aaaf927fe 100644 --- a/crates/stackable-operator/src/eos/mod.rs +++ b/crates/stackable-operator/src/eos/mod.rs @@ -1,6 +1,8 @@ use chrono::{DateTime, Utc}; +use futures::FutureExt; use snafu::{ResultExt, Snafu}; use stackable_shared::time::Duration; +use tokio::select; use tracing::{Level, instrument}; /// Available options to configure a [`EndOfSupportChecker`]. @@ -114,33 +116,51 @@ impl EndOfSupportChecker { /// /// It is recommended to run the end-of-support checker via [`futures::try_join!`] or /// [`tokio::join`] alongside other futures (eg. for controllers). - pub async fn run(self) { + pub async fn run(self, shutdown_signal: F) + where + F: Future, + { // Immediately return if the end-of-support checker is disabled. if self.disabled { return; } - // Construct an interval which can be polled. let mut interval = tokio::time::interval(self.interval.into()); + let shutdown_signal = shutdown_signal.fuse(); + tokio::pin!(shutdown_signal); + loop { - // TODO: Add way to stop from the outside - // The first tick ticks immediately. - interval.tick().await; - let now = Utc::now(); - - tracing::info_span!( - "checking end-of-support state", - eos.interval = self.interval.to_string(), - eos.now = now.to_rfc3339(), - ); - - // Continue the loop and wait for the next tick to run the check again. - if now <= self.eos_datetime { - continue; + select! { + // We used a biased polling strategy to always check if a + // shutdown signal was received before polling the EoS check + // interval. + biased; + + _ = &mut shutdown_signal => { + tracing::trace!("received shutdown signal"); + break; + } + + // The first tick ticks immediately. + _ = interval.tick() => { + let now = Utc::now(); + + tracing::info_span!( + "checking end-of-support state", + eos.interval = self.interval.to_string(), + eos.now = now.to_rfc3339(), + ); + + // Continue the loop and wait for the next tick to run the + // check again. + if now <= self.eos_datetime { + continue; + } + + self.emit_warning(now); + } } - - self.emit_warning(now); } } From fac828c4f668bf583222d4a08ece5f2009bba222 Mon Sep 17 00:00:00 2001 From: Techassi Date: Fri, 30 Jan 2026 13:48:01 +0100 Subject: [PATCH 2/3] chore(operator): Add changelog entry --- crates/stackable-operator/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index 1739c9a9c..125660dc9 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- BREAKING: Add support to gracefully shutdown `EosChecker`. + `EosChecker::run` now requires passing a shutdown signal, which is any `Future` ([#1146]). + +[#1146]: https://github.com/stackabletech/operator-rs/pull/1146 + ## [0.104.0] - 2026-01-26 ### Added From 4336aa869d5087a41d4176a7a86edbb53bd5c6a6 Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 3 Feb 2026 09:30:10 +0100 Subject: [PATCH 3/3] chore: Apply suggestion Co-authored-by: Malte Sander --- crates/stackable-operator/src/eos/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/eos/mod.rs b/crates/stackable-operator/src/eos/mod.rs index aaaf927fe..76f453e15 100644 --- a/crates/stackable-operator/src/eos/mod.rs +++ b/crates/stackable-operator/src/eos/mod.rs @@ -132,7 +132,7 @@ impl EndOfSupportChecker { loop { select! { - // We used a biased polling strategy to always check if a + // We use a biased polling strategy to always check if a // shutdown signal was received before polling the EoS check // interval. biased;