79731009

Date: 2025-08-10 05:30:15
Score: 0.5
Natty:
Report link

Might be a little jumpy reading this I was going through docs but short answer: Don’t detach the Actix server. Own shutdown yourself, pass a cancel signal to your queue, and await both tasks. Also disable Actix’s built-in signal handlers so Ctrl+C is under your control.

Your on the right track here. what you could try is: (a) a single place to own shutdown, (b) a signal you can pass to your queue so it can stop gracefully, and (c) awaiting both tasks to completion after you request shutdown. Don’t “fire-and-forget” the Actix server future.keep its JoinHandle and await it after stop(true), guaranteeing it’s fully shut down before main returns. You can have a shared token so you can exit cleanly

I believe the issue is with Actix's built in signals. You start the server and leave it running without awaiting its shutdown. The queue worker stops, but the HTTP server keeps running. May want to dig into Actix docs here. https://actix.rs/docs/server#graceful-shutdown the doc explains why your current setup goes wonky. Axtix has its own signal handlers, so CTRL-C is not "gracefull", windows doesn't send SIGTERM with Ctrl-c. So you have to approaches: A> own the shutdown yourself or B> let Actix keep its handlers (Unix- "gracefull via SIGTERM), don't disable signals and send SIGTERM, still keep and await the server task handle so nothing stays

Do I need to avoid rt::spawn?
Yes—don’t detach the server. Either run it directly in select! or spawn it and await the join handle after you call stop(true).

  1. Call .disable_signals() on the HttpServer (so Actix doesn’t install its own Ctrl+C handler), and

  2. Show a tiny code snippet that keeps the server’s JoinHandle, sends a cancel token to the queue, calls stop(true), and then awaits both tasks.

use actix_web::{App, HttpServer};
use tokio_util::sync::CancellationToken;

#[actix_web::main]
async fn main() -> anyhow::Result<()> {
    let server = HttpServer::new(|| App::new())
        .disable_signals()          // you own shutdown
        .shutdown_timeout(30)       // graceful window
        .bind(("0.0.0.0", 8080))?
        .run();
    let handle = server.handle();

    let cancel = CancellationToken::new();
    let cancel_q = cancel.clone();

    // spawn both but KEEP the JoinHandles
    let srv_task = tokio::spawn(async move { server.await.map_err(anyhow::Error::from) });
    let queue_task = tokio::spawn(async move {
        // your queue loop should watch cancel_q.cancelled().await
        run_queue(cancel_q).await
    });

    tokio::select! {
        _ = tokio::signal::ctrl_c() => {}
        // optionally: react if the server crashes:
        res = &mut async { srv_task.await } => {
            eprintln!("server exited early: {:?}", res);
        }
    }

    // trigger graceful shutdown
    handle.stop(true).await;  // waits up to shutdown_timeout for in-flight reqs
    cancel.cancel();          // tell the queue to finish and exit

    // ensure nothing is left running
    let _ = srv_task.await;
    let _ = queue_task.await;
    Ok(())
}
Reasons:
  • Blacklisted phrase (0.5): I need
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • Low reputation (1):
Posted by: David Czerepak