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 diff --git a/crates/stackable-operator/src/eos/mod.rs b/crates/stackable-operator/src/eos/mod.rs index 243cb365a..76f453e15 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 use 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); } }