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 if RT.get().is_some() {
84 return Err(anyhow::anyhow!("Worker already initialized"));
85 }
86
87 let rt = RT.try_insert(config.create_runtime()?).map_err(|_| {
91 anyhow::anyhow!("Failed to create worker; Only a single Worker should ever be created")
92 })?;
93
94 let runtime = Runtime::from_handle(rt.handle().clone())?;
95 Ok(Worker { runtime, config })
96 }
97
98 pub fn runtime_from_existing() -> anyhow::Result<Runtime> {
104 let rt = Self::ensure_process_runtime()?;
105 let handle = rt.handle().clone();
106
107 match RTCONFIG.get() {
114 Some(config) if !COMPUTE_CLAIMED.swap(true, Ordering::SeqCst) => {
115 Runtime::from_handle_with_config(handle, config)
116 }
117 _ => Runtime::from_handle(handle),
118 }
119 }
120
121 pub fn ensure_process_runtime() -> anyhow::Result<&'static tokio::runtime::Runtime> {
126 if let Some(rt) = RT.get() {
128 return Ok(rt);
129 }
130
131 RT.get_or_try_init(|| -> anyhow::Result<tokio::runtime::Runtime> {
133 let config = RuntimeConfig::from_settings()?;
134 tracing::info!("dynamo runtime configuration: {config}");
135 let rt = config.create_runtime()?;
136 let _ = RTCONFIG.set(config);
137 Ok(rt)
138 })
139 }
140
141 pub fn has_existing_runtime() -> bool {
146 RT.get().is_some()
147 }
148
149 pub fn tokio_runtime(&self) -> anyhow::Result<&'static tokio::runtime::Runtime> {
150 RT.get()
151 .ok_or_else(|| anyhow::anyhow!("Worker not initialized"))
152 }
153
154 pub fn runtime(&self) -> &Runtime {
155 &self.runtime
156 }
157
158 pub fn execute<F, Fut>(self, f: F) -> anyhow::Result<()>
159 where
160 F: FnOnce(Runtime) -> Fut + Send + 'static,
161 Fut: Future<Output = anyhow::Result<()>> + Send + 'static,
162 {
163 let runtime = self.runtime.clone();
164 runtime.secondary().block_on(self.execute_internal(f))??;
165 runtime.shutdown();
166 Ok(())
167 }
168
169 pub async fn execute_async<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 let task = self.execute_internal(f);
176 task.await??;
177 runtime.shutdown();
178 Ok(())
179 }
180
181 fn execute_internal<F, Fut>(self, f: F) -> JoinHandle<anyhow::Result<()>>
184 where
185 F: FnOnce(Runtime) -> Fut + Send + 'static,
186 Fut: Future<Output = anyhow::Result<()>> + Send + 'static,
187 {
188 let runtime = self.runtime.clone();
189 let primary = runtime.primary();
190 let secondary = runtime.secondary();
191
192 let timeout = std::env::var(env_worker::DYN_WORKER_GRACEFUL_SHUTDOWN_TIMEOUT)
193 .ok()
194 .and_then(|s| s.parse::<u64>().ok())
195 .unwrap_or({
196 if cfg!(debug_assertions) {
197 DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_DEBUG
198 } else {
199 DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_RELEASE
200 }
201 });
202
203 INIT.set(Mutex::new(Some(secondary.spawn(async move {
204 tokio::spawn(signal_handler(runtime.primary_token().clone()));
206
207 let cancel_token = runtime.child_token();
208 let (mut app_tx, app_rx) = tokio::sync::oneshot::channel::<()>();
209
210 let task: JoinHandle<anyhow::Result<()>> = primary.spawn(async move {
212 let _rx = app_rx;
213 f(runtime).await
214 });
215
216 tokio::select! {
217 _ = cancel_token.cancelled() => {
218 tracing::debug!("{SHUTDOWN_MESSAGE}");
219 tracing::debug!("{} {} seconds", SHUTDOWN_TIMEOUT_MESSAGE, timeout);
220 }
221
222 _ = app_tx.closed() => {
223 }
224 };
225
226 let result = tokio::select! {
227 result = task => {
228 result
229 }
230
231 _ = tokio::time::sleep(tokio::time::Duration::from_secs(timeout)) => {
232 tracing::debug!("Application did not shutdown in time; terminating");
233 std::process::exit(911);
234 }
235 }?;
236
237 match &result {
238 Ok(_) => {
239 tracing::debug!("Application shutdown successfully");
240 }
241 Err(e) => {
242 tracing::error!("Application shutdown with error: {:?}", e);
243 }
244 }
245
246 result
247 }))))
248 .expect("Failed to spawn application task");
249
250 INIT
251 .get()
252 .expect("Application task not initialized")
253 .lock()
254 .take()
255 .expect("Application initialized; but another thread is awaiting it; Worker.execute() can only be called once")
256 }
257
258 pub fn from_current() -> anyhow::Result<Worker> {
259 if RT.get().is_some() {
260 return Err(anyhow::anyhow!("Worker already initialized"));
261 }
262 let runtime = Runtime::from_current()?;
263 let config = RuntimeConfig::from_settings()?;
264 Ok(Worker { runtime, config })
265 }
266}
267
268async fn signal_handler(cancel_token: CancellationToken) -> anyhow::Result<()> {
270 let ctrl_c = async {
271 signal::ctrl_c().await?;
272 anyhow::Ok(())
273 };
274
275 let sigterm = async {
276 signal::unix::signal(signal::unix::SignalKind::terminate())?
277 .recv()
278 .await;
279 anyhow::Ok(())
280 };
281
282 tokio::select! {
283 _ = ctrl_c => {
284 tracing::info!("Ctrl+C received, starting graceful shutdown");
285 },
286 _ = sigterm => {
287 tracing::info!("SIGTERM received, starting graceful shutdown");
288 },
289 _ = cancel_token.cancelled() => {
290 tracing::debug!("CancellationToken triggered; shutting down");
291 },
292 }
293
294 cancel_token.cancel();
296
297 Ok(())
298}