13 releases (7 breaking)

Uses new Rust 2024

0.11.0 Feb 5, 2026
0.10.0 Nov 15, 2025
0.9.0 Nov 15, 2025
0.7.5 Jul 24, 2025
0.7.1 Nov 24, 2024

#553 in HTTP server

Download history 39/week @ 2025-12-28 23/week @ 2026-01-04 94/week @ 2026-01-11 149/week @ 2026-01-18 67/week @ 2026-01-25 136/week @ 2026-02-01 141/week @ 2026-02-08 66/week @ 2026-02-15 79/week @ 2026-02-22 315/week @ 2026-03-01 157/week @ 2026-03-08 114/week @ 2026-03-15 269/week @ 2026-03-22 147/week @ 2026-03-29 173/week @ 2026-04-05 131/week @ 2026-04-12

725 downloads per month
Used in 4 crates

MIT license

39KB
527 lines

MAD - Lifecycle Manager for Rust Applications

Crates.io Documentation License: MIT

A simple lifecycle manager for long-running Rust applications. Run multiple services concurrently with graceful shutdown handling.

Installation

[dependencies]
notmad = "0.10.0"
tokio = { version = "1", features = ["full"] }

Quick Start

use notmad::{Component, Mad};
use tokio_util::sync::CancellationToken;

struct MyService;

impl Component for MyService {
    async fn run(&self, cancellation: CancellationToken) -> Result<(), notmad::MadError> {
        println!("Service running...");
        cancellation.cancelled().await;
        println!("Service stopped");
        Ok(())
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    Mad::builder()
        .add(MyService)
        .run()
        .await?;
    Ok(())
}

Basic Usage

Axum Web Server with Graceful Shutdown

Here's how to run an Axum server with MAD's graceful shutdown:

use axum::{Router, routing::get};
use notmad::{Component, ComponentInfo};
use tokio_util::sync::CancellationToken;

struct WebServer {
    port: u16,
}

impl Component for WebServer {
    fn info(&self) -> ComponentInfo {
        format!("WebServer:{}", self.port).into()
    }

    async fn run(&self, cancel: CancellationToken) -> Result<(), notmad::MadError> {
        let app = Router::new().route("/", get(|| async { "Hello, World!" }));

        let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", self.port))
            .await?;

        println!("Listening on http://0.0.0.0:{}", self.port);

        // Run server with graceful shutdown
        axum::serve(listener, app)
            .with_graceful_shutdown(async move {
                cancel.cancelled().await;
                println!("Shutting down server...");
            })
            .await?;

        Ok(())
    }
}

Run Multiple Services

Mad::builder()
    .add(WebServer { port: 8080 })
    .add(WebServer { port: 8081 })
    .run()
    .await?;

Use Functions as Components

Mad::builder()
    .add_fn(|cancel| async move {
        println!("Running...");
        cancel.cancelled().await;
        Ok(())
    })
    .run()
    .await?;

Lifecycle Hooks

Components support optional setup and cleanup phases:

impl Component for DatabaseService {
    async fn setup(&self) -> Result<(), notmad::MadError> {
        println!("Connecting to database...");
        Ok(())
    }

    async fn run(&self, cancel: CancellationToken) -> Result<(), notmad::MadError> {
        cancel.cancelled().await;
        Ok(())
    }

    async fn close(&self) -> Result<(), notmad::MadError> {
        println!("Closing database connection...");
        Ok(())
    }
}

Migration from v0.10

Breaking Changes

  1. name()info(): Returns ComponentInfo instead of Option<String>

    // Before
    fn name(&self) -> Option<String> { Some("my-service".into()) }
    
    // After
    fn info(&self) -> ComponentInfo { "my-service".into() }
    
  2. No more async-trait: Remove the dependency and #[async_trait] attribute

    // Before
    #[async_trait]
    impl Component for MyService { }
    
    // After
    impl Component for MyService { }
    

Examples

See examples directory for complete working examples.

License

MIT - see LICENSE

Dependencies

~8–12MB
~132K SLoC