Skip to content

Commit ba62ef0

Browse files
committed
wip: graceful shutdown
1 parent c83c0d4 commit ba62ef0

File tree

6 files changed

+81
-29
lines changed

6 files changed

+81
-29
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ hyper-util = { version = "0.1.10", features = ["full"] }
5656
ignore = "0.4.20"
5757
indicatif = "0.17.3"
5858
indoc = "2.0.1"
59+
nix = "0.30"
5960
percent-encoding = "2.2"
6061
portpicker = "0.1.1"
6162
pretty_assertions = "1.3.0"

cargo-shuttle/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ hyper-util = { workspace = true }
4040
ignore = { workspace = true }
4141
indicatif = { workspace = true }
4242
indoc = { workspace = true }
43+
nix = { workspace = true, features = ["signal"] }
4344
portpicker = { workspace = true }
4445
regex = { workspace = true }
4546
reqwest = { workspace = true }

cargo-shuttle/src/lib.rs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,8 @@ impl Shuttle {
14401440
.kill_on_drop(true)
14411441
.spawn()
14421442
.context("spawning runtime process")?;
1443+
#[allow(unused)]
1444+
let pid = runtime.id().context("getting runtime's PID")?;
14431445

14441446
// Start background tasks for reading runtime's stdout and stderr
14451447
let raw = run_args.raw;
@@ -1492,7 +1494,7 @@ impl Shuttle {
14921494
});
14931495

14941496
#[cfg(target_family = "unix")]
1495-
let exit_result = {
1497+
let (exit_result, interrupted) = {
14961498
let mut sigterm_notif =
14971499
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
14981500
.expect("Can not get the SIGTERM signal receptor");
@@ -1501,20 +1503,20 @@ impl Shuttle {
15011503
.expect("Can not get the SIGINT signal receptor");
15021504
tokio::select! {
15031505
exit_result = runtime.wait() => {
1504-
Some(exit_result)
1506+
(Some(exit_result), false)
15051507
}
15061508
_ = sigterm_notif.recv() => {
1507-
eprintln!("Received SIGTERM. Killing the runtime...");
1508-
None
1509+
eprintln!("Received SIGTERM. Shutting down the runtime...");
1510+
(None, false)
15091511
},
15101512
_ = sigint_notif.recv() => {
1511-
eprintln!("Received SIGINT. Killing the runtime...");
1512-
None
1513+
eprintln!("Received SIGINT. Shutting down the runtime...");
1514+
(None, true)
15131515
}
15141516
}
15151517
};
15161518
#[cfg(target_family = "windows")]
1517-
let exit_result = {
1519+
let (exit_result, interrupted) = {
15181520
let mut ctrl_break_notif = tokio::signal::windows::ctrl_break()
15191521
.expect("Can not get the CtrlBreak signal receptor");
15201522
let mut ctrl_c_notif =
@@ -1527,27 +1529,27 @@ impl Shuttle {
15271529
.expect("Can not get the CtrlShutdown signal receptor");
15281530
tokio::select! {
15291531
exit_result = runtime.wait() => {
1530-
Some(exit_result)
1532+
(Some(exit_result), false)
15311533
}
15321534
_ = ctrl_break_notif.recv() => {
15331535
eprintln!("Received ctrl-break.");
1534-
None
1536+
(None, false)
15351537
},
15361538
_ = ctrl_c_notif.recv() => {
15371539
eprintln!("Received ctrl-c.");
1538-
None
1540+
(None, true)
15391541
},
15401542
_ = ctrl_close_notif.recv() => {
15411543
eprintln!("Received ctrl-close.");
1542-
None
1544+
(None, false)
15431545
},
15441546
_ = ctrl_logoff_notif.recv() => {
15451547
eprintln!("Received ctrl-logoff.");
1546-
None
1548+
(None, false)
15471549
},
15481550
_ = ctrl_shutdown_notif.recv() => {
15491551
eprintln!("Received ctrl-shutdown.");
1550-
None
1552+
(None, false)
15511553
}
15521554
}
15531555
};
@@ -1562,6 +1564,29 @@ impl Shuttle {
15621564
bail!("Failed to wait for runtime process to exit: {e}");
15631565
}
15641566
None => {
1567+
#[cfg(target_family = "unix")]
1568+
{
1569+
if interrupted {
1570+
nix::sys::signal::kill(
1571+
nix::unistd::Pid::from_raw(pid as i32),
1572+
nix::sys::signal::SIGINT,
1573+
)
1574+
.context("Sending SIGINT to runtime process")?;
1575+
match tokio::time::timeout(Duration::from_secs(30), runtime.wait()).await {
1576+
Ok(exit_result) => {
1577+
debug!("Runtime exited {:?}", exit_result)
1578+
}
1579+
Err(_) => {
1580+
eprintln!("Runtime shutdown timed out. Sending SIGKILL");
1581+
runtime.kill().await?;
1582+
}
1583+
};
1584+
} else {
1585+
runtime.kill().await?;
1586+
}
1587+
}
1588+
1589+
#[cfg(target_family = "windows")]
15651590
runtime.kill().await?;
15661591
}
15671592
}

runtime/src/rt.rs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::{
33
iter::FromIterator,
44
net::{IpAddr, Ipv4Addr, SocketAddr},
55
process::exit,
6+
time::Duration,
67
};
78

89
use anyhow::Context;
@@ -253,16 +254,28 @@ pub async fn start(
253254
//
254255
info!("Starting service");
255256

256-
let service_bind = service.bind(service_addr);
257-
258257
#[cfg(target_family = "unix")]
259-
let interrupted = {
258+
async fn shutdown_signal() {
260259
let mut sigterm_notif =
261260
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
262261
.expect("Can not get the SIGTERM signal receptor");
263262
let mut sigint_notif =
264263
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
265264
.expect("Can not get the SIGINT signal receptor");
265+
tokio::select! {
266+
_ = sigterm_notif.recv() => {
267+
tracing::warn!("Runtime received SIGTERM. Shutting down...");
268+
},
269+
_ = sigint_notif.recv() => {
270+
tracing::warn!("Runtime received SIGINT. Shutting down...");
271+
}
272+
}
273+
}
274+
275+
let service_bind = service.clone().bind(service_addr);
276+
277+
#[cfg(target_family = "unix")]
278+
let interrupted = {
266279
tokio::select! {
267280
res = service_bind => {
268281
if let Err(e) = res {
@@ -272,12 +285,7 @@ pub async fn start(
272285
tracing::warn!("Service terminated on its own. Shutting down the runtime...");
273286
false
274287
}
275-
_ = sigterm_notif.recv() => {
276-
tracing::warn!("Received SIGTERM. Shutting down the runtime...");
277-
true
278-
},
279-
_ = sigint_notif.recv() => {
280-
tracing::warn!("Received SIGINT. Shutting down the runtime...");
288+
_ = shutdown_signal() => {
281289
true
282290
}
283291
}
@@ -306,19 +314,19 @@ pub async fn start(
306314
_ = ctrl_break_notif.recv() => {
307315
tracing::warn!("Received ctrl-break. Shutting down the runtime...");
308316
true
309-
},
317+
}
310318
_ = ctrl_c_notif.recv() => {
311319
tracing::warn!("Received ctrl-c. Shutting down the runtime...");
312320
true
313-
},
321+
}
314322
_ = ctrl_close_notif.recv() => {
315323
tracing::warn!("Received ctrl-close. Shutting down the runtime...");
316324
true
317-
},
325+
}
318326
_ = ctrl_logoff_notif.recv() => {
319327
tracing::warn!("Received ctrl-logoff. Shutting down the runtime...");
320328
true
321-
},
329+
}
322330
_ = ctrl_shutdown_notif.recv() => {
323331
tracing::warn!("Received ctrl-shutdown. Shutting down the runtime...");
324332
true
@@ -327,7 +335,19 @@ pub async fn start(
327335
};
328336

329337
if interrupted {
330-
return 10;
338+
match tokio::time::timeout(Duration::from_secs(60), service.shutdown()).await {
339+
Err(_) => {
340+
tracing::error!("Service graceful shutdown timed out internally");
341+
return 11;
342+
}
343+
Ok(Err(shutdown_err)) => {
344+
tracing::error!("Service shutdown error: {shutdown_err}");
345+
return 12;
346+
}
347+
_ => {
348+
return 10;
349+
}
350+
};
331351
}
332352

333353
0

service/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,14 @@ impl<R: Serialize + DeserializeOwned + Send> IntoResource<R> for R {
107107
/// An `Into<Service>` implementor is what is returned in the `shuttle_runtime::main` macro
108108
/// in order to run it on the Shuttle servers.
109109
#[async_trait]
110-
pub trait Service: Send {
110+
pub trait Service: Send + Clone {
111111
/// This function is run exactly once on startup of a deployment.
112112
///
113113
/// The passed [`SocketAddr`] receives proxied HTTP traffic from your Shuttle subdomain (or custom domain).
114114
/// Binding to the address is only relevant if this service is an HTTP server.
115-
async fn bind(mut self, addr: SocketAddr) -> Result<(), error::Error>;
115+
async fn bind(self, addr: SocketAddr) -> Result<(), error::Error>;
116+
117+
async fn shutdown(self) -> Result<(), CustomError> {
118+
Ok(())
119+
}
116120
}

0 commit comments

Comments
 (0)