Linux Daemon in Swift

Writing a Proper Linux Daemon in Swift with Signal Handling
Creating a production-grade daemon on Linux requires more than just running a program in the background. A correct implementation must detach from the controlling terminal, manage OS signals, maintain lifecycle files, and behave predictably under systemd or other supervisor tools.
This article explains the architecture behind signal_14.swift, a fully functional Swift-based Linux daemon. It covers daemonization, signal handling, heartbeat management, and debugging techniques using LLDB.
Key Concepts
Daemonization
A proper Linux daemon uses the classic double-fork strategy with session detachment:
- Fork once so the parent exits.
- Execute
setsid()to detach from the terminal. - Fork again so the daemon can never reacquire a terminal.
- Redirect
stdin,stdout, andstderrto/dev/null.
// Standard double-fork daemonization pattern.
if fork() > 0 { exit(0) }
setsid()
if fork() > 0 { exit(0) }
freopen("/dev/null", "r", stdin)
freopen("/dev/null", "w", stdout)
freopen("/dev/null", "w", stderr)
PID Files and Heartbeat Files
Daemons typically maintain two important files:
- PID file: Helps systemd, supervisor, or scripts locate the running process.
- Heartbeat (HB) file: A simple way to track liveness without watching logs.
signal_14.swift writes a timestamp to a heartbeat file every N seconds:
let hbPath = "/tmp/swift_daemon.hb"
func writeHeartbeat() {
let ts = "\(Date().timeIntervalSince1970)\n"
try? ts.write(toFile: hbPath, atomically: true, encoding: .utf8)
}
This makes it trivial to monitor:
watch -n 1 cat /tmp/swift_daemon.hb
Signal Handling
A daemon must respond correctly to OS signals. A minimal set includes:
- SIGTERM: Trigger graceful shutdown.
- SIGINT: Same as SIGTERM when not attached to a terminal.
- SIGHUP: Reload configuration.
- SIGUSR1 / SIGUSR2: Custom actions.
Swift on Linux uses sigaction for signal handling:
var action = sigaction()
action.__sigaction_handler = unsafeBitCast(handleSignal, to: sigaction.__Unnamed_union___sigaction_handler.self)
sigaction(SIGTERM, &action, nil)
sigaction(SIGINT, &action, nil)
The callback:
func handleSignal(_ sig: Int32) {
switch sig {
case SIGTERM, SIGINT:
shouldRun = false
case SIGHUP:
reloadConfig()
case SIGUSR1:
rotateLogs()
default:
break
}
}
This design ensures the daemon remains responsive and predictable under load or during shutdown.
Main Loop
The daemon's event loop should:
- Run until a termination signal arrives.
- Write a heartbeat periodically.
- Perform business logic without blocking.
while shouldRun {
writeHeartbeat()
sleep(2)
}
The loop avoids writing to stdout or stderr because these are redirected to /dev/null. Any diagnostic output goes to a log file.
Log File Redirection
A production daemon must not print to the terminal. Instead, create or append logs to a file:
let logPath = "/tmp/swift_daemon.log"
freopen(logPath, "a+", stdout)
freopen(logPath, "a+", stderr)
This ensures logs survive restarts and can be tailed:
tail -f /tmp/swift_daemon.log
Testing the Daemon with LLDB
LLDB makes it possible to debug a running daemon:
1. Start daemon normally:
./signal_14.swift &
2. Attach LLDB:
lldb -p $(pidof signal_14.swift)
3. Inspect state:
(lldb) bt
(lldb) frame variable
(lldb) p shouldRun
4. Detach without killing:
(lldb) detach
LLDB is safe for production-grade debugging because Swift binaries expose rich symbol information.
systemd Integration
Place the following unit file in /etc/systemd/system/swift-daemon.service:
[Unit]
Description=Swift Linux Daemon
[Service]
ExecStart=/usr/local/bin/signal_14
Restart=always
PIDFile=/tmp/swift_daemon.pid
[Install]
WantedBy=multi-user.target
Then enable:
sudo systemctl daemon-reload
sudo systemctl enable swift-daemon
sudo systemctl start swift-daemon
To monitor:
systemctl status swift-daemon
journalctl -u swift-daemon -f
Why Swift Works Well for Daemons
Swift provides:
- Memory safety with zero-cost abstractions.
- Predictable performance.
- Access to the full POSIX API.
- Clean, modern syntax for systems engineering.
This allows you to combine low-level Linux process control with high-level Swift design patterns.
Summary
A production-grade Linux daemon written in Swift requires:
- Double-fork and session detachment.
- PID and heartbeat lifecycle files.
- Robust, POSIX-compliant signal management.
- Background logging to a file.
- A responsive main loop.
- Optional LLDB and systemd integration.
signal_14.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.




