{"id":17209334,"url":"https://github.com/de-vri-es/async-shutdown-rs","last_synced_at":"2025-06-26T12:31:53.235Z","repository":{"id":46637683,"uuid":"406815081","full_name":"de-vri-es/async-shutdown-rs","owner":"de-vri-es","description":"one-stop solution for async shutdown (runtime agnostic)","archived":false,"fork":false,"pushed_at":"2024-03-27T13:44:56.000Z","size":63,"stargazers_count":20,"open_issues_count":3,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-06-01T12:58:25.996Z","etag":null,"topics":["async","hacktoberfest","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/de-vri-es.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-15T15:04:57.000Z","updated_at":"2025-02-19T14:37:49.000Z","dependencies_parsed_at":"2024-10-15T02:51:27.013Z","dependency_job_id":"7449f359-7903-44b5-90b8-53085771c0d2","html_url":"https://github.com/de-vri-es/async-shutdown-rs","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/de-vri-es/async-shutdown-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de-vri-es%2Fasync-shutdown-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de-vri-es%2Fasync-shutdown-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de-vri-es%2Fasync-shutdown-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de-vri-es%2Fasync-shutdown-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/de-vri-es","download_url":"https://codeload.github.com/de-vri-es/async-shutdown-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/de-vri-es%2Fasync-shutdown-rs/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262068030,"owners_count":23253714,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","hacktoberfest","rust"],"created_at":"2024-10-15T02:51:21.168Z","updated_at":"2025-06-26T12:31:53.214Z","avatar_url":"https://github.com/de-vri-es.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# async-shutdown\n\nRuntime agnostic one-stop solution for graceful shutdown in asynchronous code.\n\nThis crate addresses two separate but related problems regarding graceful shutdown:\n* You have to be able to stop running futures when a shutdown signal is given.\n* You have to be able to wait for futures to finish potential clean-up.\n* You want to know why the shutdown was triggered (for example to set your process exit code).\n\nAll of these problems are handled by the [`ShutdownManager`] struct.\n\n## Stopping running futures\nYou can get a future to wait for the shutdown signal with [`ShutdownManager::wait_shutdown_triggered()`].\nIn this case you must write your async code to react to the shutdown signal appropriately.\n\nAlternatively, you can wrap a future to be cancelled (by being dropped) when the shutdown is triggered with [`ShutdownManager::wrap_cancel()`].\nThis doesn't require the wrapped future to know anything about the shutdown signal,\nbut it also doesn't allow the future to run custom shutdown code.\n\nTo trigger the shutdown signal, simply call [`ShutdownManager::trigger_shutdown(reason)`][`ShutdownManager::trigger_shutdown()`].\nThe shutdown reason can be any type, as long as it implements [`Clone`].\nIf you want to pass a non-[`Clone`] object or an object that is expensive to clone, you can wrap it in an [`Arc`].\n\n## Waiting for futures to complete.\nYou may also want to wait for some futures to complete before actually shutting down instead of just dropping them.\nThis might be important to cleanly shutdown and prevent data loss.\nYou can do that with [`ShutdownManager::wait_shutdown_complete()`].\nThat function returns a future that only completes when the shutdown is \"completed\".\n\nYou must also prevent the shutdown from completing too early by calling [`ShutdownManager::delay_shutdown_token()`] or [`ShutdownManager::wrap_delay_shutdown()`].\nThe [`ShutdownManager::delay_shutdown_token()`] function gives you a [`DelayShutdownToken`] which prevents the shutdown from completing.\nTo allow the shutdown to finish, simply drop the token.\nAlternatively, [`ShutdownManager::wrap_delay_shutdown()`] wraps an existing future,\nand will prevent the shutdown from completing until the future either completes or is dropped.\n\nNote that you can only delay the shutdown completion if it has not completed already.\nIf the shutdown is already complete those functions will return an error.\n\nYou can also use a token to wrap a future with [`DelayShutdownToken::wrap_future()`].\nIf you already have a token, this allows you to wrap a future without having to worry that the shutdown might already be completed.\n\n## Automatically triggering shutdowns\nYou can also trigger a shutdown automatically using a [`TriggerShutdownToken`].\nCall [`ShutdownManager::trigger_shutdown_token()`] to obtain the token.\nWhen the token is dropped, a shutdown is triggered.\n\nYou can use [`ShutdownManager::wrap_trigger_shutdown()`] or [`TriggerShutdownToken::wrap_future()`] to wrap a future.\nWhen the wrapped future completes (or when it is dropped) it will trigger a shutdown.\nThis can be used as a convenient way to trigger a shutdown when a vital task stops.\n\n## Futures versus Tasks\nBe careful when using `JoinHandles` as if they're a regular future.\nDepending on your async runtime, when you drop a `JoinHandle` this doesn't normally cause the task to stop.\nIt may simply detach the join handle from the task, meaning that your task is still running.\nIf you're not careful, this could still cause data loss on shutdown.\nAs a rule of thumb, you should usually wrap futures *before* you spawn them on a new task.\n\n## Example\n\nThis example is a tokio-based TCP echo server.\nIt simply echos everything it receives from a peer back to that same peer,\nand it uses this crate for graceful shutdown.\n\nThis example is also available in the repository as under the name [`tcp-echo-server`] if you want to run it locally.\n\n[`tcp-echo-server`]: https://github.com/de-vri-es/async-shutdown-rs/blob/main/examples/tcp-echo-server.rs\n\n```rust\nuse async_shutdown::ShutdownManager;\nuse std::net::SocketAddr;\nuse tokio::io::{AsyncReadExt, AsyncWriteExt};\nuse tokio::net::{TcpListener, TcpStream};\n\n#[tokio::main]\nasync fn main() {\n    // Create a new shutdown object.\n    // We will clone it into all tasks that need it.\n    let shutdown = ShutdownManager::new();\n\n    // Spawn a task to wait for CTRL+C and trigger a shutdown.\n    tokio::spawn({\n        let shutdown = shutdown.clone();\n        async move {\n            if let Err(e) = tokio::signal::ctrl_c().await {\n                eprintln!(\"Failed to wait for CTRL+C: {}\", e);\n                std::process::exit(1);\n            } else {\n                eprintln!(\"\\nReceived interrupt signal. Shutting down server...\");\n                shutdown.trigger_shutdown(0).ok();\n            }\n        }\n    });\n\n    // Run the server and set a non-zero exit code if we had an error.\n    let exit_code = match run_server(shutdown.clone(), \"[::]:9372\").await {\n        Ok(()) =\u003e {\n            shutdown.trigger_shutdown(0).ok();\n        },\n        Err(e) =\u003e {\n            eprintln!(\"Server task finished with an error: {}\", e);\n            shutdown.trigger_shutdown(1).ok();\n        },\n    };\n\n    // Wait for clients to run their cleanup code, then exit.\n    // Without this, background tasks could be killed before they can run their cleanup code.\n    let exit_code = shutdown.wait_shutdown_complete().await;\n\n    std::process::exit(exit_code);\n}\n\nasync fn run_server(shutdown: ShutdownManager\u003ci32\u003e, bind_address: \u0026str) -\u003e std::io::Result\u003c()\u003e {\n    let server = TcpListener::bind(\u0026bind_address).await?;\n    eprintln!(\"Server listening on {}\", bind_address);\n\n    // Simply use `wrap_cancel` for everything, since we do not need clean-up for the listening socket.\n    // See `handle_client` for a case where a future is given the time to perform logging after the shutdown was triggered.\n    while let Ok(connection) = shutdown.wrap_cancel(server.accept()).await {\n        let (stream, address) = connection?;\n        tokio::spawn(handle_client(shutdown.clone(), stream, address));\n    }\n\n    Ok(())\n}\n\nasync fn handle_client(shutdown: ShutdownManager\u003ci32\u003e, mut stream: TcpStream, address: SocketAddr) {\n    eprintln!(\"Accepted new connection from {}\", address);\n\n    // Make sure the shutdown doesn't complete until the delay token is dropped.\n    //\n    // Getting the token will fail if the shutdown has already started,\n    // in which case we just log a message and return.\n    //\n    // If you already have a future that should be allowed to complete,\n    // you can also use `shutdown.wrap_delay_shutdown(...)`.\n    // Here it is easier to use a token though.\n    let _delay_token = match shutdown.delay_shutdown_token() {\n        Ok(token) =\u003e token,\n        Err(_) =\u003e {\n            eprintln!(\"Shutdown already started, closing connection with {}\", address);\n            return;\n        }\n    };\n\n    // Now run the echo loop, but cancel it when the shutdown is triggered.\n    match shutdown.wrap_cancel(echo_loop(\u0026mut stream)).await {\n        Ok(Err(e)) =\u003e eprintln!(\"Error in connection {}: {}\", address, e),\n        Ok(Ok(())) =\u003e eprintln!(\"Connection closed by {}\", address),\n        Err(_exit_code) =\u003e eprintln!(\"Shutdown triggered, closing connection with {}\", address),\n    }\n\n    // The delay token will be dropped here, allowing the shutdown to complete.\n}\n\nasync fn echo_loop(stream: \u0026mut TcpStream) -\u003e std::io::Result\u003c()\u003e {\n    // Echo everything we receive back to the peer in a loop.\n    let mut buffer = vec![0; 512];\n    loop {\n        let read = stream.read(\u0026mut buffer).await?;\n        if read == 0 {\n            break;\n        }\n        stream.write(\u0026buffer[..read]).await?;\n    }\n\n    Ok(())\n}\n```\n\n[`ShutdownManager`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html\n[`ShutdownManager::wait_shutdown_triggered()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html#method.wait_shutdown_triggered\n[`ShutdownManager::wrap_cancel()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html#method.wrap_cancel\n[`ShutdownManager::trigger_shutdown()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html#method.trigger_shutdown\n[`Clone`]: https://doc.rust-lang.org/stable/std/clone/trait.Clone.html\n[`Arc`]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html\n[`ShutdownManager::wait_shutdown_complete()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html#method.wait_shutdown_complete\n[`ShutdownManager::delay_shutdown_token()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html#method.delay_shutdown_token\n[`ShutdownManager::wrap_delay_shutdown()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html#method.wrap_delay_shutdown\n[`DelayShutdownToken`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.DelayShutdownToken.html\n[`DelayShutdownToken::wrap_future()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.DelayShutdownToken.html#method.wrap_future\n[`TriggerShutdownToken`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.TriggerShutdownToken.html\n[`ShutdownManager::trigger_shutdown_token()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html#method.trigger_shutdown_token\n[`ShutdownManager::wrap_trigger_shutdown()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.ShutdownManager.html#method.wrap_trigger_shutdown\n[`TriggerShutdownToken::wrap_future()`]: https://docs.rs/async-shutdown/latest/async_shutdown/struct.TriggerShutdownToken.html#method.wrap_future\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fde-vri-es%2Fasync-shutdown-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fde-vri-es%2Fasync-shutdown-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fde-vri-es%2Fasync-shutdown-rs/lists"}