Living Document Notice
Published 2026-11-03. The evolving architecture, revisions, and connected notes for this dispatch live in the Stax Digital Garden.
The Real Cost of Background Daemons
Summary
Running background daemons continuously on personal computers demands strict resource discipline. Poorly engineered services leak heap allocations, leave stale lockfiles after sudden crashes, and spin CPU cycles when laptops transition between sleep and wake states. The Tender daemon minimizes system impact through allocator tuning, signal trapping, and deterministic lifecycle hooks.
By employing manual heap trimming, kernel file descriptor locks, and sleep event listeners, the daemon limits its resident memory footprint to under 15 megabytes. It terminates cleanly on system shutdown and resumes vault watching immediately upon machine wake.
Memory Fragmentation in Long-Running Processes
Personal workstations run for weeks between restarts. In long-running background processes, naive memory management leads to heap fragmentation. Standard memory allocators such as glibc’s ptmalloc retain virtual memory arenas after freeing objects, causing resident set size (RSS) to climb over time.
To maintain a minimal footprint, the Tender daemon implements explicit memory discipline:
- Arena Trimming: Invoking
malloc_trim(0)on Linux following batch operations. - Specialized Allocators: Utilizing jemalloc or mimalloc configured with aggressive page decay options.
- String Interning: Reusing allocations for repetitive paths and metadata keys across the note graph.
+--------------------------------------------------------------------+
| Daemon Lifecycle Transitions |
| |
| +---------------------------------------------+ |
| | Cold Start | |
| +----------------------+----------------------+ |
| | |
| v |
| +---------------------------------------------+ |
| | Lockfile Acquisition via flock(LOCK_EX) | |
| +----------------------+----------------------+ |
| | |
| v |
| +-------------------> [ Running Event Loop ] <-----------------+ |
| | (Sleep on inotify/epoll) | |
| | | | |
| | +---------------+---------------+ | |
| | | | | |
| | OS Sleep Event v Signal Caught v | |
| | +---------------------------+ +----------------------------+ | |
| | | Suspend Watch Descriptors | | SIGTERM / SIGINT Received | | |
| | +-------------+-------------+ +---------------+------------+ | |
| | | | | |
| | OS Wake Event v v | |
| | +---------------------------+ +----------------------------+ | |
| +-+ Flush Stale Sockets & Sync| | Drain WAL & flock(LOCK_UN) | | |
| +---------------------------+ +---------------+------------+ | |
| | | |
| v | |
| ( Clean Exit ) | |
+--------------------------------------------------------------------+
These measures prevent the daemon from expanding beyond 15MB RSS during normal operation, even when tracking vaults containing over 50,000 files.
Stale Lockfiles and flock Semantics
Many tools manage daemon lifecycles using text-based PID files (/var/run/app.pid). If the host system loses power, the PID file remains on disk. Upon reboot, a different process may receive that same PID, causing the daemon to assume another instance is running and abort startup.
The Tender daemon avoids fragile PID files by relying on kernel-managed file descriptor locks via flock:
int lock_fd = open("/run/user/1000/tender.lock", O_RDWR | O_CREAT, 0600);
if (lock_fd < 0) {
perror("Unable to open lockfile");
exit(1);
}
if (flock(lock_fd, LOCK_EX | LOCK_NB) != 0) {
if (errno == EWOULDBLOCK) {
fprintf(stderr, "Another Tender instance is already running.\n");
exit(0);
}
perror("flock failed");
exit(1);
}The operating system kernel binds flock locks directly to the open file table entry. If the daemon process crashes or terminates unexpectedly, the kernel releases the lock automatically. Stale files on disk never prevent the service from restarting cleanly.
POSIX Signal Trapping and Controlled Drain
When the host system shuts down or restarts, the init system dispatches SIGTERM to active daemons, followed shortly by SIGKILL if the process fails to exit within a grace period.
The Tender daemon intercepts SIGTERM and SIGINT, executing a structured shutdown sequence:
| Shutdown Phase | Time Budget | Operation Performed | Error Mitigation |
|---|---|---|---|
| Phase 1: Unbind IPC | 50ms | Close Unix domain socket and unlink file | Reject new client requests |
| Phase 2: Halt Inotify | 50ms | Deregister kernel watch descriptors | Stop processing fresh file events |
| Phase 3: Drain Journal | 500ms | Flush memory buffers to disk via fdatasync | Prevent uncommitted torn writes |
| Phase 4: Release Lock | 10ms | Execute flock(fd, LOCK_UN) and exit | Clean system resource handoff |
Trapping termination signals ensures that file buffers flush completely before process shutdown:
use signal_hook::{consts::signal::*, iterator::Signals};
pub fn handle_signals(mut signals: Signals, cancel_token: CancellationToken) {
for sig in signals.forever() {
match sig {
SIGTERM | SIGINT => {
log::info!("Termination signal caught; beginning ordered drain");
cancel_token.cancel();
break;
}
_ => {}
}
}
}Sleep and Wake Transitions
Laptops frequently close and open lids throughout the day. During system sleep, network interfaces drop without sending TCP FIN packets, and hardware timers freeze.
The Tender daemon listens for OS power notifications via D-Bus on Linux or IOPMrootDomain on macOS. When the system prepares to sleep, the daemon pauses remote sync workers. Upon wake, the daemon flushes stale network sockets and triggers a filesystem diff sweep, ensuring immediate synchronization without socket timeout stalls.
- Directus Target: tender
- Garden Source Reference: Daemon Lifecycle Management, POSIX Signal Handling, Memory Footprint Optimization, MOC - Ingestion & Capture, MOC - Local-First Systems and Synchronization, MOC - Bosun PKM Tools