1use super::{CancellationToken, Runtime, RuntimeConfig};
24
25use futures::Future;
26use once_cell::sync::OnceCell;
27use parking_lot::Mutex;
28use std::sync::atomic::{AtomicBool, Ordering};
29use std::time::Duration;
30use tokio::{signal, task::JoinHandle};
31
32static RT: OnceCell<tokio::runtime::Runtime> = OnceCell::new();
40
41static RTCONFIG: OnceCell<RuntimeConfig> = OnceCell::new();
47
48static COMPUTE_CLAIMED: AtomicBool = AtomicBool::new(false);
51
52static INIT: OnceCell<Mutex<Option<tokio::task::JoinHandle<anyhow::Result<()>>>>> = OnceCell::new();
53
54use crate::config::environment_names::worker as env_worker;
55
56const SHUTDOWN_MESSAGE: &str =
57 "Application received shutdown signal; attempting to gracefully shutdown";
58const SHUTDOWN_TIMEOUT_MESSAGE: &str =
59 "Use DYN_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT to control the graceful shutdown timeout";
60
61pub const DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_DEBUG: u64 = 5;
63
64pub const DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_RELEASE: u64 = 30;
66
67#[derive(Debug, Clone)]
68pub struct Worker {
69 runtime: Runtime,
70 config: RuntimeConfig,
71}
72
73impl Worker {
74 pub fn from_settings() -> anyhow::Result<Worker> {
76 let config = RuntimeConfig::from_settings()?;
77 Worker::from_config(config)
78 }
79
80 pub fn from_config(config: RuntimeConfig) -> anyhow::Result<Worker> {
82 RuntimeConfig::validate_parser_environment()?;
83 if RT.get().is_some() {
85 return Err(anyhow::anyhow!("Worker already initialized"));
86 }
87
88 let rt = RT.try_insert(config.create_runtime()?).map_err(|_| {
92 anyhow::anyhow!("Failed to create worker; Only a single Worker should ever be created")
93 })?;
94
95 let runtime = Runtime::from_handle(rt.handle().clone())?;
96 Ok(Worker { runtime, config })
97 }
98
99 pub fn runtime_from_existing() -> anyhow::Result<Runtime> {
105 let rt = Self::ensure_process_runtime()?;
106 let handle = rt.handle().clone();
107
108 match RTCONFIG.get() {
115 Some(config) if !COMPUTE_CLAIMED.swap(true, Ordering::SeqCst) => {
116 Runtime::from_handle_with_config(handle, config)
117 }
118 _ => Runtime::from_handle(handle),
119 }
120 }
121
122 pub fn ensure_process_runtime() -> anyhow::Result<&'static tokio::runtime::Runtime> {
127 RuntimeConfig::validate_parser_environment()?;
128 if let Some(rt) = RT.get() {
130 return Ok(rt);
131 }
132
133 RT.get_or_try_init(|| -> anyhow::Result<tokio::runtime::Runtime> {
135 let config = RuntimeConfig::from_settings()?;
136 tracing::info!("dynamo runtime configuration: {config}");
137 let rt = config.create_runtime()?;
138 let _ = RTCONFIG.set(config);
139 Ok(rt)
140 })
141 }
142
143 pub fn has_existing_runtime() -> bool {
148 RT.get().is_some()
149 }
150
151 pub fn existing_process_runtime() -> Option<&'static tokio::runtime::Runtime> {
157 RT.get()
158 }
159
160 pub fn tokio_runtime(&self) -> anyhow::Result<&'static tokio::runtime::Runtime> {
161 RT.get()
162 .ok_or_else(|| anyhow::anyhow!("Worker not initialized"))
163 }
164
165 pub fn runtime(&self) -> &Runtime {
166 &self.runtime
167 }
168
169 pub fn execute<F, Fut>(self, f: F) -> anyhow::Result<()>
170 where
171 F: FnOnce(Runtime) -> Fut + Send + 'static,
172 Fut: Future<Output = anyhow::Result<()>> + Send + 'static,
173 {
174 let runtime = self.runtime.clone();
175 runtime.secondary().block_on(self.execute_internal(f))??;
176 runtime.shutdown();
177 Ok(())
178 }
179
180 pub async fn execute_async<F, Fut>(self, f: F) -> anyhow::Result<()>
181 where
182 F: FnOnce(Runtime) -> Fut + Send + 'static,
183 Fut: Future<Output = anyhow::Result<()>> + Send + 'static,
184 {
185 let runtime = self.runtime.clone();
186 let task = self.execute_internal(f);
187 task.await??;
188 runtime.shutdown();
189 Ok(())
190 }
191
192 fn execute_internal<F, Fut>(self, f: F) -> JoinHandle<anyhow::Result<()>>
195 where
196 F: FnOnce(Runtime) -> Fut + Send + 'static,
197 Fut: Future<Output = anyhow::Result<()>> + Send + 'static,
198 {
199 let runtime = self.runtime.clone();
200 let primary = runtime.primary();
201 let secondary = runtime.secondary();
202
203 let timeout = std::env::var(env_worker::DYN_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT)
204 .ok()
205 .and_then(|s| s.parse::<u64>().ok())
206 .unwrap_or({
207 if cfg!(debug_assertions) {
208 DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_DEBUG
209 } else {
210 DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_RELEASE
211 }
212 });
213
214 INIT.set(Mutex::new(Some(secondary.spawn(async move {
215 tokio::spawn(signal_handler(runtime.primary_token().clone()));
217
218 let cancel_token = runtime.child_token();
219 let (mut app_tx, app_rx) = tokio::sync::oneshot::channel::<()>();
220
221 let task: JoinHandle<anyhow::Result<()>> = primary.spawn(async move {
223 let _rx = app_rx;
224 f(runtime).await
225 });
226
227 tokio::select! {
228 _ = cancel_token.cancelled() => {
229 tracing::debug!("{SHUTDOWN_MESSAGE}");
230 tracing::debug!("{} {} seconds", SHUTDOWN_TIMEOUT_MESSAGE, timeout);
231 }
232
233 _ = app_tx.closed() => {
234 }
235 };
236
237 let result = tokio::select! {
238 result = task => {
239 result
240 }
241
242 _ = tokio::time::sleep(tokio::time::Duration::from_secs(timeout)) => {
243 tracing::debug!("Application did not shutdown in time; terminating");
244 std::process::exit(911);
245 }
246 }?;
247
248 match &result {
249 Ok(_) => {
250 tracing::debug!("Application shutdown successfully");
251 }
252 Err(e) => {
253 tracing::error!("Application shutdown with error: {:?}", e);
254 }
255 }
256
257 result
258 }))))
259 .expect("Failed to spawn application task");
260
261 INIT
262 .get()
263 .expect("Application task not initialized")
264 .lock()
265 .take()
266 .expect("Application initialized; but another thread is awaiting it; Worker.execute() can only be called once")
267 }
268
269 pub fn from_current() -> anyhow::Result<Worker> {
270 if RT.get().is_some() {
271 return Err(anyhow::anyhow!("Worker already initialized"));
272 }
273 let runtime = Runtime::from_current()?;
274 let config = RuntimeConfig::from_settings()?;
275 Ok(Worker { runtime, config })
276 }
277}
278
279async fn signal_handler(cancel_token: CancellationToken) -> anyhow::Result<()> {
281 let ctrl_c = async {
282 signal::ctrl_c().await?;
283 anyhow::Ok(())
284 };
285
286 let sigterm = async {
287 signal::unix::signal(signal::unix::SignalKind::terminate())?
288 .recv()
289 .await;
290 anyhow::Ok(())
291 };
292
293 tokio::select! {
294 _ = ctrl_c => {
295 tracing::info!("Ctrl+C received, starting graceful shutdown");
296 },
297 _ = sigterm => {
298 tracing::info!("SIGTERM received, starting graceful shutdown");
299 },
300 _ = cancel_token.cancelled() => {
301 tracing::debug!("CancellationToken triggered; shutting down");
302 },
303 }
304
305 cancel_token.cancel();
307
308 Ok(())
309}