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.
✅ 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).
- 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)
- 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 guardedunsafewhen thewindows-monitoringfeature is enabled
- 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
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 | 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.
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
}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);
}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.
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 overcrossbeam-channel(enabled) orstd::sync::mpsc(fallback).SubsystemManager::enable_events()andSubsystemManager::try_next_event()— non-blocking event polling.SubsystemManager::subscribe_events()— get aReceiver<SubsystemEvent>to poll from another task when events are enabled.
Event type:
SubsystemEvent::StateChanged { id, name, state, at }
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
}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)
# }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 = trueLoad the configuration:
let config = Config::load_from_file("daemon.toml")?;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"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))
}
}#[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);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
# }proc-daemon is built around zero-copy principles:
- Arc-based sharing: Configuration and state shared via
Arcto 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
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
proc-daemon implements a sophisticated shutdown coordination system:
- Signal Reception: Cross-platform signal handling
- Graceful Notification: All subsystems notified simultaneously
- Coordinated Shutdown: Subsystems shut down in dependency order
- Timeout Management: Configurable graceful and force timeouts
- Resource Cleanup: Automatic cleanup of resources and handles
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 benchEnable 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.tomlin the working directory. - The watcher starts automatically when
Config.hot_reload = true.
All runnable examples are available in the examples/ directory. Here are the key ones:
A minimal daemon that demonstrates basic setup and graceful shutdown:
cargo run --example simple --features tokioMulti-subsystem daemon showcasing:
- Custom subsystem implementations
- Health checks
- Restart policies
- Metrics collection
cargo run --example comprehensive --features tokio,metricsConfiguration 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 collection and introspection example:
cargo run --example metrics_server --features "tokio metrics"proc-daemon is designed for extreme performance:
cargo bench- 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
- 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.
- Zero unresolved vulnerabilities: clean
cargo audit. Three documented allow-list entries cover the optionalasync-stdfeature path (legacy, removal in v2.0.0) and a dev-onlyrandadvisory viaproptest— none reach production code paths. pprofcorrectness: bumped to0.14in v1.0.1, resolving RUSTSEC-2024-0408 (unsoundstd::slice::from_raw_partsusage in the optional CPU profiler).- Memory Safety: Safe Rust by default (
#![deny(unsafe_code)]); Windows monitoring uses guardedunsafeonly when explicitly enabled via thewindows-monitoringfeature. - No lock poisoning in hot paths: v1.1.0 migrated remaining
std::sync::Mutex/RwLocksites toparking_lotequivalents, 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.
# 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 geigerSee .cargo/audit.toml for documented allowed vs. disallowed vulnerabilities.
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- 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.
- API Reference Complete documentation and examples.
- Code Principles guidelines for contribution & development.
- 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.
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.