Skip to main content

Command Palette

Search for a command to run...

Writing a Proper Linux Daemon in Swift with Signal Handling and Cleanup

Updated
3 min read
Writing a Proper Linux Daemon in Swift with Signal Handling and Cleanup

Creating a production-grade Linux daemon requires more than simply running a program in the background.
A correct daemon must detach from the controlling terminal, manage lifecycle files, respond predictably to operating system signals, and clean up all resources before termination.

This article explains the architecture and implementation of signal_15.swift, a Swift-based Linux daemon that correctly implements POSIX daemonization and signal-based cleanup.
It follows the classic UNIX daemon model and demonstrates that Swift can be used for serious Linux systems programming—not just application-level code.


Key Concepts

A correct Linux daemon must address the following responsibilities:

  • Process detachment and session management
  • Lifecycle visibility using a PID file
  • Liveness indication using a heartbeat file
  • POSIX-compliant signal handling
  • Predictable and safe shutdown semantics

This implementation focuses on all of the above.


Daemonization

A proper Linux daemon follows the classic double-fork with session detachment pattern.
This ensures the process:

  • Has no controlling terminal
  • Cannot accidentally reacquire a terminal
  • Is fully detached from the user session

Why Double-Fork?

  • Prevents terminal reattachment
  • Matches traditional UNIX daemon behavior
  • Required for long-running background services

Required Steps

  1. Fork once and let the parent exit
  2. Call setsid() to create a new session
  3. Fork again so the daemon is not a session leader
  4. Redirect standard file descriptors to /dev/null

Swift Implementation

if fork() > 0 { exit(0) }
setsid()
if fork() > 0 { exit(0) }

let fd = open("/dev/null", O_RDWR)
dup2(fd, STDIN_FILENO)
dup2(fd, STDOUT_FILENO)
dup2(fd, STDERR_FILENO)

PID Files and Heartbeat Files

Lifecycle files are critical for daemon management and observability.
They allow external tools, administrators, and supervisors to reason about the daemon’s state without attaching debuggers or parsing logs.


PID File

A PID file records the process ID of the running daemon.

Purpose

  • Allows systemd, scripts, or administrators to locate the daemon process
  • Enables clean termination using kill
  • Prevents multiple instances of the daemon from running simultaneously

Behavioral Rules

  • Write the PID file only after successful daemonization
  • Remove the PID file during graceful shutdown
  • Never leave stale PID files behind

Failure to manage the PID file correctly can lead to:

  • Orphaned processes
  • Broken service restarts
  • Incorrect health reporting by supervisors

Heartbeat File

A heartbeat file proves that the daemon is alive without parsing logs.

Purpose

  • Lightweight health check mechanism
  • Human-readable liveness signal
  • Works even when logs are rotated, unavailable, or redirected

Heartbeat files are especially useful for:

  • Simple monitoring scripts
  • Debugging hung or stalled daemons
  • Environments without centralized logging

Logging Strategy

Daemons must never write to a terminal.
Once daemonized, stdin, stdout, and stderr are redirected and should not be relied upon.

  • Dedicated log file (manual rotation or logrotate)
  • syslog integration
  • journalctl (when managed via systemd)

A correct logging strategy ensures:

  • Logs survive restarts
  • Diagnostics are available post-crash
  • No accidental dependency on terminal I/O

Signal Handler Design in Swift

A production-grade daemon must handle OS termination signals explicitly.
Failing to do so results in stale PID files, leaked file descriptors, and undefined shutdown behavior.

In UNIX systems, signal handlers have strict constraints:

  • They cannot accept custom arguments
  • They must execute quickly
  • They must operate on globally accessible state

Because of this, any resources that require cleanup—such as PID files or open file handles—must be stored in global variables so that both main() and the signal handler can access them.


Global State for Cleanup

let pidFilePath = "/var/run/my-daemon.pid"
var aliveFileHandle: FileHandle? = nil

signal_15.swift demonstrates that Swift is not just for iOS or server frameworks; it is a capable systems programming language for long-running background processes on Linux.