Skip to content

A high-performance, cross-platform daemon framework for Rust. Built with an async-native architecture and robust graceful shutdown for creating resilient, production-grade services.

License

Notifications You must be signed in to change notification settings

jamesgober/proc-daemon

Repository files navigation

High-Performance Process EcoSystem for Rust

Process Daemon
RUST DAEMON FRAMEWORK

Crates.io   Crates.io Downloads   License   docs.rs   GitHub CI

A foundational framework for building high-performance, resilient daemon services in Rust. Designed for enterprise applications requiring nanosecond-level performance, bulletproof reliability, and extreme concurrency.

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: 100% safe Rust with #![deny(unsafe_code)]

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 = "0.9.0"

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

Feature Flags

Feature Description Default
tokio Tokio runtime support
async-std async-std runtime support
metrics Performance metrics collection
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
heap-profiling Optional heap profiling via dhat
full All features enabled

Quick Start

Simple Daemon

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

async fn my_service(mut shutdown: proc_daemon::ShutdownHandle) -> proc_daemon::Result<()> {
    let mut counter = 0;
    
    loop {
        tokio::select! {
            _ = shutdown.cancelled() => {
                tracing::info!("Service shutting down gracefully after {} iterations", counter);
                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<()> {
    let config = Config::new()?;
    
    Daemon::builder(config)
        .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 = "0.9.0", 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 = "0.9.0", 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 = "0.9.0", 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::{Daemon, Config, Subsystem, ShutdownHandle, RestartPolicy};
use std::pin::Pin;
use std::future::Future;
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"
    }

    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<()> {
    while !shutdown.is_shutdown() {
        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<()> {
    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;

let config = Config::builder()
    .name("my-daemon")
    .log_level(LogLevel::Info)
    .json_logging(true)
    .shutdown_timeout(Duration::from_secs(30))
    .worker_threads(8)
    .enable_metrics(true)
    .hot_reload(true)
    .build()?;

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

struct DatabasePool {
    connections: Arc<AtomicUsize>,
}

impl Subsystem for DatabasePool {
    fn run(&self, mut 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"
    }

    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;

let signal_config = SignalConfig::new()
    .with_sighup()  // Enable SIGHUP handling
    .without_sigint()  // Disable SIGINT
    .with_custom_handler(12, "Custom signal");

Daemon::builder(config)
    .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
Loading

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.


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

  • Memory Safety: 100% safe Rust with no unsafe code
  • Signal Safety: Async signal handling prevents race conditions
  • Resource Limits: Configurable limits prevent resource exhaustion
  • Graceful Degradation: Continues operating even when subsystems fail

Development Setup

git clone https://github.com/jamesgober/proc-daemon.git
cd proc-daemon
cargo build --all-features
cargo test --all-features


Documentation:



Acknowledgments

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



DEVELOPMENT & CONTRIBUTION

We welcome contributions! Please see CONTRIBUTING.md for guidelines.




Rust Logo
Built with Rust.


⚖️ License

Licensed under the Apache License, version 2.0 (the "License"); you may not use this software, including, but not limited to the source code, media files, ideas, techniques, or any other associated property or concept belonging to, associated with, or otherwise packaged with this software except in compliance with the License.

You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the LICENSE file included with this project for the specific language governing permissions and limitations under the License.


COPYRIGHT © 2025 JAMES GOBER.

About

A high-performance, cross-platform daemon framework for Rust. Built with an async-native architecture and robust graceful shutdown for creating resilient, production-grade services.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published