actix / actix-web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.
https://actix.rs
Apache License 2.0
21.57k stars 1.67k forks source link

Sockets and pipes open after stopping server/system #1787

Open fivethousand opened 3 years ago

fivethousand commented 3 years ago

Expected Behavior

Stopping server/system closes all sockets and pipes.

Current Behavior

Stopping the server/system (without exiting the process) leaves a number of sockets and pipes open. Therefore, restarting the server increases the number of open sockets and pipes.

Steps to Reproduce

use actix_web::{middleware, App, HttpServer};

pub fn main() -> std::io::Result<()> {
    // - inside loop: each iteration opens 22 new pipes, 0 new sockets
    // - outside loop: each iteration opens 2 new pipes, 4 new sockets
    let mut system = actix_web::rt::System::new("main");

    loop {
        system.block_on::<_, std::io::Result<()>>(async move {
            HttpServer::new(move || App::new().wrap(middleware::Logger::default()))
                .bind("127.0.0.1:8080")?
                .run()
                .await
        })?;

        // doesn't stop everything...? see https://github.com/actix/actix-web/issues/1249
        actix_web::rt::System::current().stop();
    }
}

ls -l /proc/[PID]/fd contains 7 sockets and 28 pipes in the first iteration. Pressing Ctrl+C shuts down the server (and the system) and proceeds to the next iteration. This time ls -l /proc/[PID]/fd contains 11 sockets and 30 pipes.

Moving let mut system = ... into the loop avoids increasing the number of open sockets, but at the same time increases the number of open pipes by 22 in each iteration.

Context

I am trying to restart the server to change routes/handlers. Since the router is read-only, restarting the server is the only solution that does not involve writing a custom router.

Besides the issue reported here, a simple solution based on https://github.com/actix/examples/tree/master/shutdown-server (slightly extended to differentiate stop/restart) works well.

Your Environment

fivethousand commented 3 years ago

"Workaround"

Start server/system in a child process. Sockets and pipes are closed when the child process terminates.

fakeshadow commented 3 years ago

actix_rt::System does not actually control the socket accept thread. You can use the server handle to send stop command to it.

This is an example.

use actix_web::{middleware, App, HttpServer};

pub fn main() -> std::io::Result<()> {
    let mut system = actix_web::rt::System::new("main");

    loop {
        let server = system.block_on(async move {
            HttpServer::new(move || App::new().wrap(middleware::Logger::default()))
                .bind("127.0.0.1:8080")
                .unwrap()
                .run()
        });
        server.stop(true);
        actix_web::rt::System::current().stop();
    }
}
fakeshadow commented 3 years ago

Also one thing to notice is that Server::stop would also call System::current().stop() with a delay. So you can remove the extra explict call and it would also work.