Skip to main content

Command Palette

Search for a command to run...

Linux Daemon in Swift

Updated
4 min read
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, and stderr to /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:

  1. PID file: Helps systemd, supervisor, or scripts locate the running process.
  2. 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.


More from this blog

DevNation

66 posts

Blog on System Programming Languages