From d1ed4330badddaceafea12be6635c58b8d86b872 Mon Sep 17 00:00:00 2001 From: Tuntii <121901995+Tuntii@users.noreply.github.com> Date: Fri, 20 Feb 2026 03:58:50 +0000 Subject: [PATCH] docs: add graceful shutdown recipe and fix background jobs - Create `docs/cookbook/src/recipes/graceful_shutdown.md` with a detailed recipe on graceful shutdown using `run_with_shutdown`. - Update `docs/cookbook/src/recipes/background_jobs.md` to fix API usage, macro syntax, and generics. - Update `docs/cookbook/src/learning/curriculum.md` to include a mini-project for graceful shutdown in Module 10. - Update `docs/cookbook/src/SUMMARY.md` to include the new recipe. - Update `docs/.agent/docs_coverage.md` and `docs/.agent/last_run.json`. - Generate `docs/.agent/run_report_2026-02-20.md`. --- docs/.agent/docs_coverage.md | 3 +- docs/.agent/last_run.json | 4 +- docs/.agent/run_report_2026-02-20.md | 22 ++++ docs/cookbook/src/SUMMARY.md | 1 + docs/cookbook/src/learning/curriculum.md | 3 + docs/cookbook/src/recipes/background_jobs.md | 25 ++-- .../cookbook/src/recipes/graceful_shutdown.md | 116 ++++++++++++++++++ 7 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 docs/.agent/run_report_2026-02-20.md create mode 100644 docs/cookbook/src/recipes/graceful_shutdown.md diff --git a/docs/.agent/docs_coverage.md b/docs/.agent/docs_coverage.md index 5850880..5bf9646 100644 --- a/docs/.agent/docs_coverage.md +++ b/docs/.agent/docs_coverage.md @@ -12,6 +12,7 @@ | HTTP/3 (QUIC) | `docs/cookbook/src/recipes/http3_quic.md` | `rustapi-core/src/http3.rs` (`Http3Server`) | OK | | File Uploads | `docs/cookbook/src/recipes/file_uploads.md` | `rustapi-core/src/multipart.rs` (`Multipart`) | OK | | Compression | `docs/cookbook/src/recipes/compression.md` | `rustapi-core/src/middleware/compression.rs` (`CompressionLayer`) | OK | +| Graceful Shutdown | `docs/cookbook/src/recipes/graceful_shutdown.md` | `rustapi-core/src/server.rs` (`run_with_shutdown`) | OK | | **OpenAPI** | | | | | Schema Derivation | `docs/cookbook/src/crates/rustapi_openapi.md` | `rustapi-macros/src/derive_schema.rs` (`#[derive(Schema)]`) | OK | | References ($ref) | `docs/cookbook/src/recipes/openapi_refs.md` | `rustapi-openapi/src/schema.rs` (`SchemaRef`) | OK | @@ -27,7 +28,7 @@ | Middleware (Advanced) | `recipes/advanced_middleware.md` | `rustapi-extras/src/{rate_limit, dedup, cache}` | OK | | **Jobs** | | | | | Job Queue (Crate) | `crates/rustapi_jobs.md` | `rustapi-jobs` | OK | -| Background Jobs (Recipe) | `recipes/background_jobs.md` | `rustapi-jobs` | OK | +| Background Jobs (Recipe) | `recipes/background_jobs.md` | `rustapi-jobs` | Updated | | **Integrations** | | | | | gRPC | `recipes/grpc_integration.md` | `rustapi-grpc` | OK | | SSR | `recipes/server_side_rendering.md` | `rustapi-view` | OK | diff --git a/docs/.agent/last_run.json b/docs/.agent/last_run.json index b2bb51d..acc1227 100644 --- a/docs/.agent/last_run.json +++ b/docs/.agent/last_run.json @@ -1,5 +1,5 @@ { "last_processed_ref": "v0.1.335", - "date": "2026-02-19", - "notes": "Deleted incorrect recipes (tuning, action pattern). Updated File Uploads and WebSockets recipes for accuracy. Expanded Learning Path with mini projects." + "date": "2026-02-20", + "notes": "Added Graceful Shutdown recipe and mini-project. Corrected Background Jobs recipe." } diff --git a/docs/.agent/run_report_2026-02-20.md b/docs/.agent/run_report_2026-02-20.md new file mode 100644 index 0000000..7e36221 --- /dev/null +++ b/docs/.agent/run_report_2026-02-20.md @@ -0,0 +1,22 @@ +# Run Report: 2026-02-20 + +**Status**: Success +**Version Target**: v0.1.335 (No change) +**Focus**: Continuous Improvement (Cookbook & Learning Path) + +## Changes + +### Documentation Updates +- **Updated**: `docs/cookbook/src/recipes/background_jobs.md` - Corrected API usage (`.run()` instead of `.serve()`), macro syntax, and explicit generics for `enqueue`. +- **Created**: `docs/cookbook/src/recipes/graceful_shutdown.md` - New recipe covering graceful shutdown with `run_with_shutdown` and background task coordination. +- **Updated**: `docs/cookbook/src/learning/curriculum.md` - Added "The Graceful Exit" mini-project to Module 10. +- **Updated**: `docs/cookbook/src/SUMMARY.md` - Added "Graceful Shutdown" to the table of contents. +- **Updated**: `docs/.agent/docs_coverage.md` - Tracked new recipe. + +## Improvements +- Verified `background_jobs.md` against codebase patterns. +- Expanded "Production Readiness" module in the learning path. + +## Next Steps +- Continue expanding Phase 4 (Enterprise Scale) mini-projects. +- Consider adding a recipe for "Custom Error Handling" as previously identified. diff --git a/docs/cookbook/src/SUMMARY.md b/docs/cookbook/src/SUMMARY.md index 12c2678..998b872 100644 --- a/docs/cookbook/src/SUMMARY.md +++ b/docs/cookbook/src/SUMMARY.md @@ -46,6 +46,7 @@ - [Production Tuning](recipes/high_performance.md) - [Response Compression](recipes/compression.md) - [Resilience Patterns](recipes/resilience.md) + - [Graceful Shutdown](recipes/graceful_shutdown.md) - [Audit Logging](recipes/audit_logging.md) - [Time-Travel Debugging (Replay)](recipes/replay.md) - [Deployment](recipes/deployment.md) diff --git a/docs/cookbook/src/learning/curriculum.md b/docs/cookbook/src/learning/curriculum.md index 9cae23d..b16570e 100644 --- a/docs/cookbook/src/learning/curriculum.md +++ b/docs/cookbook/src/learning/curriculum.md @@ -203,6 +203,9 @@ Create a `POST /register` endpoint that accepts a JSON body `{"username": "...", - **Expected Output:** A resilient API ready for deployment. - **Pitfalls:** Setting timeouts too low for slow operations. +#### 🛠️ Mini Project: "The Graceful Exit" +Implement `run_with_shutdown` handling `Ctrl+C`. Verify that long-running requests complete successfully when the server receives the signal. + #### 🧠 Knowledge Check 1. Why is timeout middleware important? 2. What command generates a production Dockerfile? diff --git a/docs/cookbook/src/recipes/background_jobs.md b/docs/cookbook/src/recipes/background_jobs.md index 020922e..f198454 100644 --- a/docs/cookbook/src/recipes/background_jobs.md +++ b/docs/cookbook/src/recipes/background_jobs.md @@ -94,9 +94,10 @@ async fn main() -> std::io::Result<()> { // 5. Build application RustApi::auto() - .with_state(queue) // Inject queue into state - .serve("127.0.0.1:3000") + .state(queue) // Inject queue into state + .run("127.0.0.1:3000") .await + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) } ``` @@ -108,11 +109,17 @@ You can now inject the `JobQueue` into your request handlers using the `State` e use rustapi_rs::prelude::*; use rustapi_jobs::JobQueue; -#[rustapi::post("/register")] +#[derive(Deserialize)] +struct RegisterRequest { + username: String, + email: String, +} + +#[rustapi_rs::post("/register")] async fn register_user( State(queue): State, Json(payload): Json, -) -> Result { +) -> Result> { // ... logic to create user in DB ... let user_id = "user_123".to_string(); // Simulated ID @@ -121,19 +128,13 @@ async fn register_user( queue.enqueue::(WelcomeEmailData { user_id, email: payload.email, - }).await.map_err(|e| ApiError::InternalServerError(e.to_string()))?; + }).await.map_err(|e| ApiError::internal(e.to_string()))?; - Ok(Json(json!({ + Ok(Json(serde_json::json!({ "status": "registered", "message": "Welcome email will be sent shortly" }))) } - -#[derive(Deserialize)] -struct RegisterRequest { - username: String, - email: String, -} ``` ## Resilience and Retries diff --git a/docs/cookbook/src/recipes/graceful_shutdown.md b/docs/cookbook/src/recipes/graceful_shutdown.md new file mode 100644 index 0000000..d299b88 --- /dev/null +++ b/docs/cookbook/src/recipes/graceful_shutdown.md @@ -0,0 +1,116 @@ +# Graceful Shutdown + +Graceful shutdown is essential for production applications to ensure that in-flight requests are completed before the server stops. This prevents data loss and provides a smooth experience for users during deployments or restarts. + +## How it Works + +When a shutdown signal (like `SIGTERM` or `Ctrl+C`) is received: +1. The server stops accepting new connections. +2. Existing connections are allowed to finish their current request. +3. Background tasks are notified (if configured). +4. The application exits once all connections are closed or a timeout is reached. + +## Implementation + +RustAPI supports graceful shutdown via the `.run_with_shutdown()` method, which accepts a `Future` that resolves when shutdown should occur. + +### Basic Example + +Here is how to implement graceful shutdown listening for `Ctrl+C`: + +```rust +use rustapi_rs::prelude::*; +use tokio::signal; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let app = RustApi::auto(); + + println!("Server running on http://127.0.0.1:3000"); + + app.run_with_shutdown("127.0.0.1:3000", shutdown_signal()) + .await?; + + Ok(()) +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } + + println!("Shutdown signal received, starting graceful shutdown..."); +} +``` + +## Shutdown with Background Tasks + +If you have background tasks (like `rustapi-jobs` workers), you should coordinate their shutdown as well. You can use `tokio_util::sync::CancellationToken` or a broadcast channel. + +```rust +use rustapi_rs::prelude::*; +use tokio::signal; +use tokio_util::sync::CancellationToken; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let token = CancellationToken::new(); + + // Start a background task + let child_token = token.child_token(); + tokio::spawn(async move { + loop { + tokio::select! { + _ = child_token.cancelled() => { + println!("Background task shutting down"); + return; + } + _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => { + println!("Working..."); + } + } + } + }); + + // Run server + RustApi::auto() + .run_with_shutdown("0.0.0.0:3000", async { + shutdown_signal().await; + token.cancel(); // Notify background tasks + }) + .await?; + + println!("Server exited successfully"); + Ok(()) +} +``` + +## Platform Specifics + +### Docker + +When stopping a container, Docker sends `SIGTERM`. If your app doesn't exit within a timeout (default 10s), it sends `SIGKILL`. Graceful shutdown ensures your app handles `SIGTERM` correctly. + +### Kubernetes + +Kubernetes also sends `SIGTERM` before terminating a pod. It waits for the `terminationGracePeriodSeconds` (default 30s) before force-killing. + +Ensure your shutdown logic completes within these timeframes.