feat(watch): propagate SIGINT and SIGTERM signals to function if exte… · cargo-lambda/cargo-lambda@6014ceb · GitHub
Skip to content

Commit 6014ceb

Browse files
committed
feat(watch): propagate SIGINT and SIGTERM signals to function if extensions are registered
1 parent cce273c commit 6014ceb

5 files changed

Lines changed: 120 additions & 13 deletions

File tree

‎crates/cargo-lambda-watch/src/runtime/extensions_router.rs‎

Lines changed: 20 additions & 3 deletions

‎crates/cargo-lambda-watch/src/runtime/mod.rs‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,17 @@ pub(crate) fn routes() -> Router<RefRuntimeState> {
1717
Router::new()
1818
.route("/2020-01-01/extension/register", post(register_extension))
1919
// secondary route is for internal extensions
20-
// that have the function name in their path
21-
// (which we just discard currently)
2220
.route(
23-
"/:_function_name/2020-01-01/extension/register",
21+
"/:function_name/2020-01-01/extension/register",
2422
post(register_extension),
2523
)
2624
.route(
2725
"/2020-01-01/extension/event/next",
2826
get(next_extension_event),
2927
)
3028
// secondary route is for internal extensions
31-
// that have the function name in their path
32-
// (which we just discard currently)
3329
.route(
34-
"/:_function_name/2020-01-01/extension/event/next",
30+
"/:function_name/2020-01-01/extension/event/next",
3531
get(next_extension_event),
3632
)
3733
.route("/2020-08-15/logs", put(subcribe_extension_events))

‎crates/cargo-lambda-watch/src/state.rs‎

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ use std::{
1010
collections::{HashMap, HashSet, hash_map::Entry},
1111
net::SocketAddr,
1212
path::PathBuf,
13-
sync::Arc,
13+
sync::{
14+
Arc,
15+
atomic::{AtomicBool, Ordering},
16+
},
17+
time::Duration,
1418
};
1519
use tokio::sync::{Mutex, RwLock, mpsc, oneshot};
1620
use tracing::debug;
@@ -226,14 +230,20 @@ pub(crate) struct ExtensionCache {
226230
extensions: Arc<Mutex<HashMap<String, Vec<String>>>>,
227231
events: Arc<Mutex<HashMap<String, Vec<String>>>>,
228232
senders: Arc<Mutex<HashMap<String, mpsc::Sender<NextEvent>>>>,
233+
has_internal_extension: Arc<AtomicBool>,
234+
has_external_extension: Arc<AtomicBool>,
229235
}
230236

231237
impl ExtensionCache {
232-
pub async fn register(&self, events: Vec<String>) -> String {
238+
pub async fn register(&self, events: Vec<String>, extension_type: ExtensionType) -> String {
233239
let mut extensions = self.extensions.lock().await;
234240
let extension_id = Uuid::new_v4();
235241

236242
extensions.insert(extension_id.to_string(), events.clone());
243+
match extension_type {
244+
ExtensionType::Internal => self.has_internal_extension.store(true, Ordering::Relaxed),
245+
ExtensionType::External => self.has_external_extension.store(true, Ordering::Relaxed),
246+
};
237247

238248
let mut list = self.events.lock().await;
239249
for event in events {
@@ -287,4 +297,28 @@ impl ExtensionCache {
287297
}
288298
}
289299
}
300+
301+
/// Returns the allowed duration between SIGTERM and SIGKILL for the function,
302+
/// if any. This depends on whether there are any internal or external functions registered.
303+
///
304+
/// We don't currently simulate extension shutdown delay.
305+
///
306+
/// Ref:
307+
/// - https://github.com/aws-samples/graceful-shutdown-with-aws-lambda
308+
/// - https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#runtimes-lifecycle-shutdown
309+
pub async fn function_shutdown_delay(&self) -> Option<Duration> {
310+
if self.has_internal_extension.load(Ordering::Relaxed) {
311+
Some(Duration::from_millis(500))
312+
} else if self.has_external_extension.load(Ordering::Relaxed) {
313+
Some(Duration::from_millis(300))
314+
} else {
315+
None
316+
}
317+
}
318+
}
319+
320+
#[derive(Debug)]
321+
pub enum ExtensionType {
322+
Internal,
323+
External,
290324
}

‎crates/cargo-lambda-watch/src/watcher.rs‎

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use cargo_lambda_metadata::{
33
cargo::load_metadata,
44
config::{ConfigOptions, load_config_without_cli_flags},
55
};
6-
// use cargo_lambda_metadata::cargo::function_environment_metadata;
76
use ignore::create_filter;
87
use ignore_files::IgnoreFile;
98
use std::{collections::HashMap, convert::Infallible, path::PathBuf, sync::Arc, time::Duration};
@@ -128,11 +127,28 @@ async fn runtime(
128127
let ext_cache = ext_cache.clone();
129128
async move {
130129
if signals.contains(&MainSignal::Terminate) {
130+
let function_shutdown_delay = ext_cache.function_shutdown_delay().await;
131+
if let Some(delay) = function_shutdown_delay {
132+
function_graceful_shutdown_or_else_sigkill(
133+
action,
134+
MainSignal::Terminate,
135+
delay,
136+
);
137+
return Ok(());
138+
}
131139
action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit));
132140
return Ok(());
133141
}
134-
135142
if signals.contains(&MainSignal::Interrupt) {
143+
let function_shutdown_delay = ext_cache.function_shutdown_delay().await;
144+
if let Some(delay) = function_shutdown_delay {
145+
function_graceful_shutdown_or_else_sigkill(
146+
action,
147+
MainSignal::Interrupt,
148+
delay,
149+
);
150+
return Ok(());
151+
}
136152
action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit));
137153
return Ok(());
138154
}
@@ -211,6 +227,32 @@ async fn runtime(
211227
Ok(config)
212228
}
213229

230+
fn function_graceful_shutdown_or_else_sigkill(
231+
action: Action,
232+
signal_type: MainSignal,
233+
max_delay: Duration,
234+
) {
235+
tracing::debug!(
236+
?signal_type,
237+
?max_delay,
238+
"attempting graceful function shutdown"
239+
);
240+
action.outcome(Outcome::both(
241+
// send sigterm
242+
Outcome::Signal(signal_type),
243+
// race graceful shutdown against forced shutdown following max delay
244+
Outcome::race(
245+
// happy path, process exits then watchexec exits
246+
Outcome::both(Outcome::Wait, Outcome::Exit),
247+
// unhappy path, we sleep max delay then SIGKILL and exit watchexec
248+
Outcome::both(
249+
Outcome::Sleep(max_delay),
250+
Outcome::both(Outcome::Stop, Outcome::Exit),
251+
),
252+
),
253+
));
254+
}
255+
214256
fn reload_env(manifest_path: &PathBuf, bin_name: &Option<String>) -> HashMap<String, String> {
215257
let metadata = match load_metadata(manifest_path) {
216258
Ok(metadata) => metadata,

‎docs/commands/watch.md‎

Lines changed: 18 additions & 0 deletions

0 commit comments

Comments
 (0)