5

I'm writing a service with warp in Rust. When the service receives a SIGTERM signal, I'd like to have it shutdown gracefully and possibly do some logging or other work.

I have tried a number of examples and nothing works. The most promising seems to be from this issue but I cannot seem to get this to work or even compile. I suspect things have changed since this was answered.

# Cargo.toml
[dependencies]
tokio = {version = "1", features = ["full"]}
warp = "0.3"
futures = "0.3"
//! main.rs
use warp::Filter;
use futures;

fn main() {
    let (tx, rx) = tokio::sync::oneshot::channel();
    tokio::run(futures::future::lazy(move || {
        let routes = warp::any().map(|| "Hello, World!");
        let (_, server) = warp::serve(routes)
            .bind_with_graceful_shutdown(([127, 0, 0, 1], 3030), rx);
        warp::spawn(server);
    }));

    println!("Exiting!");
}
error[E0425]: cannot find function `run` in crate `tokio`
 --> src/main.rs:6:12
  |
6 |     tokio::run(futures::future::lazy(move || {
  |            ^^^ not found in `tokio`

error[E0425]: cannot find function `spawn` in crate `warp`
  --> src/main.rs:10:15
   |
10 |         warp::spawn(server);
   |               ^^^^^ not found in `warp`
   |
help: consider importing one of these items
   |
1  | use std::thread::spawn;
   |
1  | use tokio::spawn;
   |

error[E0593]: closure is expected to take 1 argument, but it takes 0 arguments
 --> src/main.rs:6:16
  |
6 |     tokio::run(futures::future::lazy(move || {
  |                ^^^^^^^^^^^^^^^^^^^^^ ------- takes 0 arguments
  |                |
  |                expected closure that takes 1 argument
  |
help: consider changing the closure to take and ignore the expected argument
  |
6 |     tokio::run(futures::future::lazy(move |_| {
  |                                           ~~~

error[E0271]: type mismatch resolving `<tokio::sync::oneshot::Receiver<_> as warp::Future>::Output == ()`
   --> src/main.rs:9:14
    |
9   |             .bind_with_graceful_shutdown(([127, 0, 0, 1], 3030), rx);
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `Result`
    |
    = note: expected unit type `()`
                    found enum `Result<_, tokio::sync::oneshot::error::RecvError>`
note: required by a bound in `warp::Server::<F>::bind_with_graceful_shutdown`
   --> /Users/stephen.gibson/.cargo/registry/src/github.com-1ecc6299db9ec823/warp-0.3.2/src/server.rs:281:29
    |
281 |         signal: impl Future<Output = ()> + Send + 'static,
    |                             ^^^^^^^^^^^ required by this bound in `warp::Server::<F>::bind_with_graceful_shutdown`

Any advice or better yet, updated code would be appreciated.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
DungeonTiger
  • 627
  • 1
  • 9
  • 21
  • Advice? Have a sharp look at the date [when](https://docs.rs/tokio/0.1.22/tokio/fn.run.html) that answer was posted… – Caesar Feb 25 '22 at 00:27
  • @Caesar mean that tokio and rust async ecosystem evolved and if you are new to rust be aware that in past there was no `await` and `async` keyword. So if you find old async rust code this is not how we deal with it today. This is good youtube video that can help you understand how it works today: https://www.youtube.com/watch?v=ThjvMReOXYM – S.R Feb 25 '22 at 00:41

3 Answers3

3

Thanks to everyone for your thoughts. This is the code that ended up working the way I wanted:

use warp::Filter;
use tokio::signal::unix::{signal, SignalKind};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let routes = warp::any().map(|| "Hello, World!");

    let mut stream = signal(SignalKind::terminate())?;

    let (_, server) = warp::serve(routes)
         .bind_with_graceful_shutdown(([127, 0, 0, 1], 3030), async move {
             println!("waiting for signal");
             stream.recv().await;
             println!("done waiting for signal");
         });

    match tokio::join!(tokio::task::spawn(server)).0 {
        Ok(()) => println!("serving"),
        Err(e) => println!("ERROR: Thread join error {}", e)
    };

    println!("terminating");
    Ok(())
}
DungeonTiger
  • 627
  • 1
  • 9
  • 21
3

A simpler solution, using tokio::signal::ctrl_c, a function specifically designed to sleep until a shutdown signal is received.

It works on both Unix and Windows.

use warp::Filter;

#[tokio::main]
async fn main() {
    let routes = warp::any().map(|| "Hello, World!");

    let (_addr, fut) = warp::serve(routes)
        .bind_with_graceful_shutdown(([127, 0, 0, 1], 3030), async move {
            tokio::signal::ctrl_c()
                .await
                .expect("failed to listen to shutdown signal");
        });

    fut.await;

    println!("shutting down");
}
ZunTzu
  • 7,244
  • 3
  • 31
  • 39
0

Here is example code that works. I was inspirited by wrap documentation of bind_with_graceful_shutdown

use tokio::sync::oneshot;
use warp::Filter;

#[tokio::main]
async fn main() {
    let routes = warp::any().map(|| "Hello, World!");

    let (tx, rx) = oneshot::channel();

    let (_addr, server) =
        warp::serve(routes).bind_with_graceful_shutdown(([127, 0, 0, 1], 3030), async {
            rx.await.ok();
        });

    // Spawn the server into a runtime
    tokio::task::spawn(server);

    // Later, start the shutdown...
    let _ = tx.send(());
}
S.R
  • 2,411
  • 1
  • 22
  • 33
  • This compiles thank you, but when you run it, it just exits. – DungeonTiger Feb 25 '22 at 14:12
  • @DungeonTiger You need to add logic for shutdown. In this case this code just spaw the server and immediately send the signal (`tx.send(())`) to shutdown it. After `//Later, start the shutdown` you need to add some logic for this shutdown. For example you can wait for [CTRL+C signal](https://docs.rs/tokio/latest/tokio/signal/fn.ctrl_c.html) – S.R Feb 26 '22 at 15:09