Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[feat] - Graceful shutdown #21

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ tera = "1.20.0"
test-context = "0.3.0"
thiserror = "2.0.6"
tokio = { version = "1.42.0", features = ["full"] }
tower = { version = "0.5.1", features = ["full"] }
tower-http = { version = "0.6.2", features = ["full"] }
tracing = { version = "0.1.41", features = ["attributes"] }
tracing-appender = "0.2.3"
tracing-bunyan-formatter = "0.3.10"
Expand All @@ -76,4 +78,4 @@ uuid = { version = "1.11.0", features = ["v4", "serde"] }
tokio-tungstenite = "0.24.0"
garde = { version = "0.20.0", features = ["full"] }
regex = "1.11.1"
wiremock = "0.6.2"
wiremock = "0.6.2"
1 change: 1 addition & 0 deletions settings/base.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[server]
addr = "0.0.0.0"
port = 8_080
grace_shutdown_secs = 10

[db]
host = "127.0.0.1"
Expand Down
1 change: 1 addition & 0 deletions settings/dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ profile = "dev"
[server]
addr = "127.0.0.1"
port = 8_080
grace_shutdown_secs = 10

[db]
host = "127.0.0.1"
Expand Down
1 change: 1 addition & 0 deletions settings/prod.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ profile = "prod"
[server]
addr = "0.0.0.0"
port = 8_080
grace_shutdown_secs = 30

[db]
host = "127.0.0.1"
Expand Down
1 change: 1 addition & 0 deletions settings/test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ profile = "test"
[server]
addr = "127.0.0.1"
port = 0
grace_shutdown_secs = 1

[db]
host = "127.0.0.1"
Expand Down
2 changes: 2 additions & 0 deletions src/configure/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use serde::Deserialize;
pub struct ServerConfig {
pub addr: String,
pub port: u16,
pub grace_shutdown_secs: i64,
}

impl ServerConfig {
Expand All @@ -31,6 +32,7 @@ pub mod tests {
let config = ServerConfig {
addr: "127.0.0.1".to_string(),
port: 1024,
grace_shutdown_secs: 10,
};
assert_eq!(config.get_http_addr(), "http://127.0.0.1:1024");
}
Expand Down
45 changes: 43 additions & 2 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use self::state::AppState;
use crate::configure::AppConfig;
use crate::error::AppResult;
use crate::router::create_router_app;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
use tracing::info;
pub mod state;
pub mod worker;

Expand All @@ -20,8 +23,46 @@ impl AppServer {
}

pub async fn run(self) -> AppResult<()> {
let router = create_router_app(self.state);
axum::serve(self.tcp, router).await?;
let shutdown = self.grace_shutdown_time();
let router = create_router_app(self.state)
.layer(TraceLayer::new_for_http()) // Visibility of the request and response, change as needed.
.layer(TimeoutLayer::new(shutdown)); // Graceful shutdown for hosting services requries N time to complete the request before shutting down.

axum::serve(self.tcp, router)
.with_graceful_shutdown(shutdown_signal())
.await?;
Ok(())
}

fn grace_shutdown_time(&self) -> std::time::Duration {
std::time::Duration::from_secs(self.state.config.server.grace_shutdown_secs as u64)
}
}

async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("Failed to install CTRL+C signal handler");
};

#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("Failed to install SIGTERM signal handler")
.recv()
.await;
};

#[cfg(not(unix))]
let terminate = std::future::pending::<()>();

tokio::select! {
_ = ctrl_c => {
info!("Received Ctrl-C signal. Shutting down...");
},
_ = terminate => {
info!("Received SIGTERM signal. Shutting down...");
},
}
}
12 changes: 7 additions & 5 deletions src/util/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ pub async fn join_all(tasks: Vec<Task>) -> AppResult {
tokio::spawn(async {
if let Err(e) = task.await {
if let Some(sender) = sender {
sender
.send(e)
.await
.unwrap_or_else(|_| unreachable!("This channel never closed."));
let _ = sender.send(e).await;
} else {
error!("A task failed: {e}.");
}
}
});
}

// Explicitly drop the sender to close the channel.
drop(sender);

// Return Ok(()) if all futures are completed without error.
match receiver.recv().await {
Some(err) => Err(err),
None => unreachable!("This channel never closed."),
None => Ok(()),
}
}