From d259177eabf75a7bdd3a84b48d3467c4680c8110 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Aug 2022 23:58:56 +0100 Subject: [PATCH] use placeholder approach to shutdown-server --- .github/workflows/linux.yml | 6 ++-- Cargo.lock | 2 ++ shutdown-server/Cargo.toml | 2 ++ shutdown-server/README.md | 27 ++++++++------- shutdown-server/src/main.rs | 67 ++++++++++++++++++++++--------------- 5 files changed, 61 insertions(+), 43 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8f22439..ec2e144 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,11 +15,11 @@ jobs: strategy: fail-fast: false matrix: - version: ['stable', 'nightly'] + version: [stable, nightly] env: - # included in example of redis, not clear its purpose - CI: true + CI: '1' + CARGO_UNSTABLE_SPARSE_REGISTRY: 'true' steps: - uses: actions/checkout@v2 diff --git a/Cargo.lock b/Cargo.lock index dd98c0c..7eefdc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5686,8 +5686,10 @@ name = "shutdown-server" version = "1.0.0" dependencies = [ "actix-web", + "actix-web-lab", "env_logger 0.9.0", "log", + "parking_lot 0.12.1", "tokio 1.20.1", ] diff --git a/shutdown-server/Cargo.toml b/shutdown-server/Cargo.toml index 1e206e7..1aaab23 100644 --- a/shutdown-server/Cargo.toml +++ b/shutdown-server/Cargo.toml @@ -6,7 +6,9 @@ description = "Send a request to the server to shut it down" [dependencies] actix-web = "4" +actix-web-lab = "0.16" env_logger = "0.9" log = "0.4" +parking_lot = "0.12" tokio = { version = "1.13.1", features = ["signal"] } diff --git a/shutdown-server/README.md b/shutdown-server/README.md index d77c566..fd43b7e 100644 --- a/shutdown-server/README.md +++ b/shutdown-server/README.md @@ -2,26 +2,27 @@ Demonstrates how to shutdown the web server in a couple of ways: -1. remotely, via http request - - Created in response to actix/actix-web#1315 +1. remotely, via HTTP request 1. sending a SIGINT signal to the server (control-c) - - actix-server natively supports SIGINT + - Actix Web servers support shutdown signals by default. [See here for more info.](https://actix.rs/docs/server#graceful-shutdown) ## Usage ### Running The Server -```sh -cd shutdown-server -cargo run --bin shutdown-server - -# Starting 8 workers -# Starting "actix-web-service-127.0.0.1:8080" service on 127.0.0.1:8080 +```console +$ cd shutdown-server +$ cargo run --bin shutdown-server +[INFO] starting HTTP server at http://localhost:8080 +[INFO] Starting 2 workers +[INFO] Actix runtime found; starting in Actix runtime ``` ### Available Routes -- [GET /hello](http://localhost:8080/hello) - - Regular hello world route -- [POST /stop](http://localhost:8080/stop) - - Calling this will shutdown the server and exit +- [`GET /hello`](http://localhost:8080/hello) + - Test hello world +- `POST /stop/true` + - Gracefully shuts down the server and exit +- `POST /stop/false` + - Forces server shutdown and exits diff --git a/shutdown-server/src/main.rs b/shutdown-server/src/main.rs index f123314..c0cfdf5 100644 --- a/shutdown-server/src/main.rs +++ b/shutdown-server/src/main.rs @@ -1,17 +1,15 @@ -use std::{sync::mpsc, thread}; - -use actix_web::{get, middleware, post, web, App, HttpResponse, HttpServer}; +use actix_web::{dev::ServerHandle, get, middleware, post, web, App, HttpResponse, HttpServer}; +use actix_web_lab::extract::Path; +use parking_lot::Mutex; #[get("/hello")] async fn hello() -> &'static str { "Hello world!" } -#[post("/stop")] -async fn stop(stopper: web::Data>) -> HttpResponse { - // make request that sends message through the Sender - stopper.send(()).unwrap(); - +#[post("/stop/{graceful}")] +async fn stop(Path(graceful): Path, stop_handle: web::Data) -> HttpResponse { + let _ = stop_handle.stop(graceful); HttpResponse::NoContent().finish() } @@ -19,33 +17,48 @@ async fn stop(stopper: web::Data>) -> HttpResponse { async fn main() -> std::io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - // create a channel - let (tx, rx) = mpsc::channel::<()>(); + // create the stop handle container + let stop_handle = web::Data::new(StopHandle::default()); log::info!("starting HTTP server at http://localhost:8080"); // start server as normal but don't .await after .run() yet - let server = HttpServer::new(move || { - // give the server a Sender in .data - App::new() - .app_data(web::Data::new(tx.clone())) - .wrap(middleware::Logger::default()) - .service(hello) - .service(stop) + let srv = HttpServer::new({ + let stop_handle = stop_handle.clone(); + + move || { + // give the server a Sender in .data + App::new() + .app_data(stop_handle.clone()) + .service(hello) + .service(stop) + .wrap(middleware::Logger::default()) + } }) .bind(("127.0.0.1", 8080))? + .workers(2) .run(); - // clone the server handle - let srv = server.handle(); - thread::spawn(move || { - // wait for shutdown signal - rx.recv().unwrap(); - - // send stop server gracefully command - srv.stop(true) - }); + // register the server handle with the stop handle + stop_handle.register(srv.handle()); // run server until stopped (either by ctrl-c or stop endpoint) - server.await + srv.await +} + +#[derive(Default)] +struct StopHandle { + inner: Mutex>, +} + +impl StopHandle { + /// Sets the server handle to stop. + pub(crate) fn register(&self, handle: ServerHandle) { + *self.inner.lock() = Some(handle); + } + + /// Sends stop signal through contained server handle. + pub(crate) fn stop(&self, graceful: bool) { + let _ = self.inner.lock().as_ref().unwrap().stop(graceful); + } }