proc-daemon 1.1.2

High-performance async daemon framework for Rust services: cross-platform signal handling, graceful shutdown, subsystem lifecycle management, hot-reload config, and structured logging. Tokio-first.
docs.rs failed to build proc-daemon-1.1.2
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: proc-daemon-1.0.0

Status

✅ Stable Release — Production-ready with zero critical vulnerabilities, comprehensive automated testing (36 unit + 5 integration tests passing on default features; 39 + 5 + 4 doc tests under --all-features), and cross-platform validation.

Latest: v1.1.2 — see the release notes and CHANGELOG.md. Prior releases: v1.1.1 (docs.rs build fix) · v1.1.0 (performance + ergonomics) · v1.0.1 (maintenance + audit).

Features

Core Capabilities

  • Zero-Copy Architecture: Minimal allocations with memory pooling for maximum performance
  • Runtime Agnostic: First-class support for both Tokio and async-std via feature flags
  • Cross-Platform: Native support for Linux, macOS, and Windows with platform-specific optimizations
  • Graceful Shutdown: Coordinated shutdown with configurable timeouts and subsystem awareness
  • Signal Handling: Robust cross-platform signal management (SIGTERM, SIGINT, SIGQUIT, SIGHUP, Windows console events)

Advanced Features

  • Subsystem Management: Concurrent subsystem lifecycle management with health checks and auto-restart
  • Configuration Hot-Reload: Dynamic configuration updates without service interruption
  • Structured Logging: High-performance tracing with JSON support and log rotation
  • Metrics Collection: Built-in performance monitoring and resource tracking
  • Memory Safety: Safe Rust by default (#![deny(unsafe_code)]); Windows monitoring uses guarded unsafe when the windows-monitoring feature is enabled

Enterprise Ready

  • High Concurrency: Built for 100,000+ concurrent operations
  • Resource Management: Intelligent memory pooling and NUMA awareness
  • Health Monitoring: Comprehensive subsystem health checks and diagnostics
  • Production Tested: Battle-tested patterns from high-scale deployments

Installation

Add this to your Cargo.toml:

[dependencies]
proc-daemon = "1.1.2"

# Optional features
proc-daemon = { version = "1.1.2", features = ["full"] }

Rust Version: Requires Rust 1.82.0 or later

Feature Flags

Feature Description Default
tokio Tokio runtime support
async-std async-std runtime support (unmaintained; best-effort legacy)
metrics Performance metrics collection and introspection
console Enhanced console output
json-logs JSON structured logging
config-watch Configuration hot-reloading
mmap-config Memory-mapped config file loading (TOML fast-path, safe fallback)
mimalloc Use mimalloc as global allocator
high-res-timing High-resolution timing via quanta
scheduler-hints Enable scheduler tuning hooks (no-op by default)
scheduler-hints-unix Best-effort Unix niceness adjustment (uses renice; no-op without privileges)
lockfree-coordination Lock-free coordination/events via crossbeam-channel
profiling Optional CPU profiling via pprof (Unix-only — target-gated; inert on Windows)
heap-profiling Optional heap profiling via dhat (cross-platform)
full All features enabled

Note: async-std is discontinued upstream; support here is best-effort and intended for existing users (slated for removal in v2.0.0). The profiling feature is target-gated to Unix — pprof relies on POSIX libc types (pthread_t, siginfo_t, ucontext_t) and will not be compiled on Windows. The heap-profiling feature (dhat) is cross-platform.

Quick Start

Simple Daemon

use proc_daemon::{Daemon, ShutdownHandle};
use std::time::Duration;

async fn my_service(mut shutdown: ShutdownHandle) -> proc_daemon::Result<()> {
    let mut counter = 0;
    loop {
        tokio::select! {
            () = shutdown.cancelled() => {
                tracing::info!("Service shutting down after {counter} iterations");
                break;
            }
            () = tokio::time::sleep(Duration::from_secs(1)) => {
                counter += 1;
                tracing::info!("Service running: iteration {counter}");
            }
        }
    }
    Ok(())
}

#[tokio::main]
async fn main() -> proc_daemon::Result<()> {
    // v1.1.0: `Daemon::new()` is the infallible default-config shortcut.
    // For explicit config, use `Daemon::builder(config)` instead.
    Daemon::new()
        .with_task("my_service", my_service)
        .run()
        .await
}

High-Resolution Timing (optional)

Enable the high-res-timing feature to access a fast, monotonic clock backed by quanta:

[dependencies]
proc-daemon = { version = "1.1.2", features = ["high-res-timing"] }
#[cfg(feature = "high-res-timing")]
{
    let t0 = proc_daemon::timing::now();
    // ... work ...
    let t1 = proc_daemon::timing::now();
    let dt = t1.duration_since(t0);
    println!("elapsed: {:?}", dt);
}

Mimalloc Global Allocator (optional)

Enable the mimalloc feature to switch the global allocator for potential performance wins in allocation-heavy workloads:

[dependencies]
proc-daemon = { version = "1.1.2", features = ["mimalloc"] }

No code changes are required—proc-daemon sets the global allocator when the feature is enabled.

Lock-free Coordination (optional)

Enable the lockfree-coordination feature to use a lock-free MPMC channel for coordination. This exposes a small channel facade and optional subsystem events for state changes.

[dependencies]
proc-daemon = { version = "1.1.2", features = ["lockfree-coordination"] }

APIs:

  • proc_daemon::coord::chan::{unbounded, try_recv} — Uniform API over crossbeam-channel (enabled) or std::sync::mpsc (fallback).
  • SubsystemManager::enable_events() and SubsystemManager::try_next_event() — non-blocking event polling.
  • SubsystemManager::subscribe_events() — get a Receiver<SubsystemEvent> to poll from another task when events are enabled.

Event type:

SubsystemEvent::StateChanged { id, name, state, at }

Multi-Subsystem Daemon

use proc_daemon::{Config, Daemon, RestartPolicy, ShutdownHandle, Subsystem};
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;

// Define a custom subsystem
struct HttpServer {
    port: u16,
}

impl Subsystem for HttpServer {
    fn run(
        &self,
        mut shutdown: ShutdownHandle,
    ) -> Pin<Box<dyn Future<Output = proc_daemon::Result<()>> + Send>> {
        let port = self.port;
        Box::pin(async move {
            tracing::info!("HTTP server starting on port {port}");
            loop {
                tokio::select! {
                    () = shutdown.cancelled() => {
                        tracing::info!("HTTP server shutting down");
                        break;
                    }
                    () = tokio::time::sleep(Duration::from_millis(100)) => {
                        // Handle HTTP requests here
                    }
                }
            }
            Ok(())
        })
    }

    fn name(&self) -> &str {
        "http_server"
    }

    // Restart with exponential backoff (1s → 60s, max 5 attempts).
    fn restart_policy(&self) -> RestartPolicy {
        RestartPolicy::ExponentialBackoff {
            initial_delay: Duration::from_secs(1),
            max_delay: Duration::from_secs(60),
            max_attempts: 5,
        }
    }
}

async fn background_worker(mut shutdown: ShutdownHandle) -> proc_daemon::Result<()> {
    loop {
        tokio::select! {
            () = shutdown.cancelled() => break,
            () = tokio::time::sleep(Duration::from_secs(5)) => {
                tracing::info!("Background work completed");
            }
        }
    }
    Ok(())
}

#[tokio::main]
async fn main() -> proc_daemon::Result<()> {
    // Note: the builder timeout setters return `Result<Self>` (timeouts are
    // validated relative to each other: graceful < force < kill). `?` chains
    // them cleanly inside `main`.
    let config = Config::builder()
        .name("multi-subsystem-daemon")
        .shutdown_timeout(Duration::from_secs(30))?
        .worker_threads(4)
        .build()?;

    Daemon::builder(config)
        .with_subsystem(HttpServer { port: 8080 })
        .with_task("background_worker", background_worker)
        .run()
        .await
}

Configuration

Programmatic Configuration

use proc_daemon::{Config, LogLevel};
use std::time::Duration;

# fn build_cfg() -> proc_daemon::Result<Config> {
let config = Config::builder()
    .name("my-daemon")
    .log_level(LogLevel::Info)
    .json_logging(true)
    // Timeouts are validated relative to each other: graceful < force < kill.
    // The setters return `Result<Self>`; `?` chains them inside a Result fn.
    .shutdown_timeout(Duration::from_secs(30))?
    .force_shutdown_timeout(Duration::from_secs(45))?
    .kill_timeout(Duration::from_secs(60))?
    .worker_threads(8)
    .enable_metrics(true)
    .hot_reload(true)
    .build()?;
# Ok(config)
# }

File Configuration (TOML)

Create a daemon.toml file:

name = "my-production-daemon"

[logging]
level = "info"
json = false
color = true
file = "/var/log/my-daemon.log"

[shutdown]
timeout_ms = 30000
force_timeout_ms = 45000
kill_timeout_ms = 60000

[performance]
worker_threads = 0  # auto-detect
thread_pinning = false
memory_pool_size = 1048576
numa_aware = false
lock_free = true

[monitoring]
enable_metrics = true
metrics_interval_ms = 1000
health_checks = true

Load the configuration:

let config = Config::load_from_file("daemon.toml")?;

Environment Variables

All configuration options can be overridden with environment variables using the DAEMON_ prefix:

export DAEMON_NAME="env-daemon"
export DAEMON_LOGGING_LEVEL="debug"
export DAEMON_SHUTDOWN_TIMEOUT_MS="60000"
export DAEMON_PERFORMANCE_WORKER_THREADS="16"

Advanced Usage

Custom Subsystems with Health Checks

use proc_daemon::{ShutdownHandle, Subsystem};
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

struct DatabasePool {
    connections: Arc<AtomicUsize>,
}

impl Subsystem for DatabasePool {
    fn run(
        &self,
        _shutdown: ShutdownHandle,
    ) -> Pin<Box<dyn Future<Output = proc_daemon::Result<()>> + Send>> {
        let _connections = Arc::clone(&self.connections);
        Box::pin(async move {
            // Database pool management logic …
            Ok(())
        })
    }

    fn name(&self) -> &str {
        "database_pool"
    }

    // Health checks are polled every `monitoring.health_check_interval_ms`
    // by the daemon main loop when `monitoring.health_checks = true`.
    fn health_check(&self) -> Option<Box<dyn Fn() -> bool + Send + Sync>> {
        let connections = Arc::clone(&self.connections);
        Some(Box::new(move || connections.load(Ordering::Acquire) > 0))
    }
}

Metrics Collection

#[cfg(feature = "metrics")]
use proc_daemon::metrics::MetricsCollector;

let collector = MetricsCollector::new();

// Increment counters
collector.increment_counter("requests_total", 1);

// Set gauge values
collector.set_gauge("active_connections", 42);

// Record timing histograms
collector.record_histogram("request_duration", Duration::from_millis(150));

// Get metrics snapshot
let snapshot = collector.get_metrics();
println!("Uptime: {:?}", snapshot.uptime);

Signal Handling Configuration

use proc_daemon::signal::SignalConfig;
use proc_daemon::Daemon;

# async fn example() -> proc_daemon::Result<()> {
let signal_config = SignalConfig::new()
    .with_sighup()                       // Enable SIGHUP handling
    .without_sigint()                    // Disable SIGINT
    .with_custom_handler(12, "Custom signal");

Daemon::new()
    .with_signal_config(signal_config)
    .run()
    .await
# }

Architecture

Zero-Copy Design

proc-daemon is built around zero-copy principles:

  • Arc-based sharing: Configuration and state shared via Arc to avoid cloning
  • Lock-free coordination: Uses atomic operations and lock-free data structures
  • Memory pooling: Pre-allocated memory pools for high-frequency operations
  • Efficient serialization: Direct memory mapping for configuration loading

Subsystem Lifecycle

graph TD
    A[Register] --> B[Starting]
    B --> C[Running]
    C --> D[Health Check]
    D --> C
    C --> E[Stopping]
    E --> F[Stopped]
    F --> G[Restart?]
    G -->|Yes| B
    G -->|No| H[Removed]
    C --> I[Failed]
    I --> G

Shutdown Coordination

proc-daemon implements a sophisticated shutdown coordination system:

  1. Signal Reception: Cross-platform signal handling
  2. Graceful Notification: All subsystems notified simultaneously
  3. Coordinated Shutdown: Subsystems shut down in dependency order
  4. Timeout Management: Configurable graceful and force timeouts
  5. Resource Cleanup: Automatic cleanup of resources and handles

Testing

Run the test suite:

# Run all tests
cargo test

# Run tests with all features
cargo test --all-features

# Run integration tests
cargo test --test integration

# Run benchmarks
cargo bench

Hot-Reload (optional)

Enable config-watch to live-reload daemon.toml at runtime (optionally combine with mmap-config for fast TOML loading). The daemon maintains a live snapshot accessible via Daemon::config_snapshot().

Run the example:

cargo run --example hot_reload --features "tokio config-watch toml mmap-config"

Notes:

  • Place daemon.toml in the working directory.
  • The watcher starts automatically when Config.hot_reload = true.

Examples

All runnable examples are available in the examples/ directory. Here are the key ones:

simple.rs

A minimal daemon that demonstrates basic setup and graceful shutdown:

cargo run --example simple --features tokio

comprehensive.rs

Multi-subsystem daemon showcasing:

  • Custom subsystem implementations
  • Health checks
  • Restart policies
  • Metrics collection
cargo run --example comprehensive --features tokio,metrics

hot_reload.rs

Configuration hot-reload demonstration that watches daemon.toml:

cargo run --example hot_reload --features "tokio config-watch toml mmap-config"

Edit daemon.toml to see the daemon pick up changes without restart.

metrics_server.rs

Metrics collection and introspection example:

cargo run --example metrics_server --features "tokio metrics"

PERFORMANCE

proc-daemon is designed for extreme performance:

Running Benchmarks

cargo bench

Typical performance:

  • Daemon Creation: ~1-5μs
  • Subsystem Registration: ~500ns per subsystem
  • Shutdown Coordination: ~10-50μs for 100 subsystems
  • Signal Handling: ~100ns latency
  • Metrics Collection: ~10ns per operation

Memory Usage

  • Base daemon: ~1-2MB
  • Per subsystem: ~4-8KB
  • Configuration: ~1-4KB
  • Signal handling: ~512B

🔗 See PERFORMANCE.md for up-to-date benchmarks, metrics, and version-over-version improvements.

Security

Security Posture (v1.1.1)

  • Zero unresolved vulnerabilities: clean cargo audit. Three documented allow-list entries cover the optional async-std feature path (legacy, removal in v2.0.0) and a dev-only rand advisory via proptest — none reach production code paths.
  • pprof correctness: bumped to 0.14 in v1.0.1, resolving RUSTSEC-2024-0408 (unsound std::slice::from_raw_parts usage in the optional CPU profiler).
  • Memory Safety: Safe Rust by default (#![deny(unsafe_code)]); Windows monitoring uses guarded unsafe only when explicitly enabled via the windows-monitoring feature.
  • No lock poisoning in hot paths: v1.1.0 migrated remaining std::sync::Mutex/RwLock sites to parking_lot equivalents, which don't poison — eliminating an entire class of latent failure modes inside subsystem and resource tracking.
  • Signal Safety: async signal handling routes platform signals into the shutdown coordinator without holding locks in handlers.
  • Resource Limits: configurable shutdown/force/kill timeouts cap any single subsystem's ability to block teardown.
  • Graceful Degradation: continues operating when individual subsystems fail; restart policies are explicit per subsystem.

Running Security Checks

# Verify no unallowed vulnerabilities
cargo audit

# Static analysis
cargo clippy --all-features --all-targets -- -D warnings

# Check for unsafe code (install with: cargo install cargo-geiger)
cargo geiger

See .cargo/audit.toml for documented allowed vs. disallowed vulnerabilities.

Development Setup

git clone https://github.com/jamesgober/proc-daemon.git
cd proc-daemon

# Build with all features enabled
cargo build --all-features

# Run all tests
cargo test --all-features

# Run linting checks
cargo clippy --all-features --all-targets -- -D warnings

# Run benchmarks
cargo bench

# Check security vulnerabilities
cargo audit

Supported Platforms

  • Linux: Full support, optimized for production
  • macOS: Full support, verified on x86_64 and ARM64
  • Windows: Supported, with caveats listed below
  • FreeBSD: Likely works but not regularly tested

Windows Note: The profiling feature is target-gated to Unix (v1.0.1+) — pprof relies on POSIX libc types (pthread_t, siginfo_t, ucontext_t). On Windows the feature is inert (not compiled). Heap profiling (heap-profiling, via dhat) is cross-platform. The scheduler-hints-unix feature is a no-op on Windows; windows-monitoring provides equivalent Win32 ToolHelp-based monitoring.

Acknowledgments

  • Inspired by production daemon patterns from high-scale deployments
  • Built on the excellent Rust async ecosystem (Tokio; async-std legacy support)
  • Configuration management powered by Figment
  • Logging via the tracing ecosystem

We welcome contributions! Please open an issue or pull request on the GitHub repository.