Lisp daemons with SBCL

While opening a REPL — from a terminal or using SLIME in Emacs — is really simple, running a daemon wasn’t obvious to me when I started programming in Common Lisp. A common setup is based on screen or detachtty, so I started with them.

Some months ago, Nikodemus Siivola wrote sb-daemon, which can be used to daemonize an SBCL process. I find this solution cleaner and simpler, and now use it for all my Common Lisp daemons.

Since I regularly need to create background processes to run various pieces of code, I wrote a Common Lisp script that makes a daemon from the current process, start a swank server, and makes sure to redirect output to files.

Pid files are stored in /var/run/sbcl-daemon/$name where $name is the name of the daemon, and log files are saved in /var/log/sbcl-daemon/$name.

(require :asdf)

(asdf:load-system :sb-daemon)
(asdf:load-system :swank)

(defvar *swank-server*)

(defun signal-handler (signal)
  (format t "~A received~%" signal)

(when (< (length sb-ext:*posix-argv*) 3)
  (error "Missing command line arguments."))

(destructuring-bind (argv0 name port) sb-ext:*posix-argv*
  (let* ((name-directory (concatenate 'string name "/"))
         (log-path (merge-pathnames name-directory #p"/var/log/sbcl-daemon/"))
         (run-path (merge-pathnames name-directory #p"/var/run/sbcl-daemon/"))
         (pid-file (concatenate 'string name ".pid")))
    (sb-daemon:daemonize :exit-parent t
                         :output (merge-pathnames "stdout.log" log-path)
                         :error (merge-pathnames "stderr.log" log-path)
                         :pidfile (merge-pathnames pid-file run-path)
                         :sigterm 'signal-handler
                         :sigabrt 'signal-handler
                         :sigint 'signal-handler))
  (setf *swank-server*
        (swank:create-server :port (parse-integer port)
                             :coding-system "utf-8-unix"
                             :dont-close t))
    (sleep 10)))

I then use the following shell script to easily create new daemons:

#!/bin/sh -e

if [ $# -lt 2 ]; then
    echo "Usage: $0 <name> <port>"
    exit 1


if [ ! -d $logpath ]; then
    echo "$logpath not found"
    exit 1
if [ ! -d $runpath ]; then
    echo "$runpath not found"
    exit 1

if [ ! -d $logpath ]; then
    echo "creating $logpath"
    mkdir $logpath
if [ ! -d $runpath ]; then
    echo "creating $runpath"
    mkdir $runpath

sbcl --script $HOME/bin/sbcl-daemon.lisp $name $port

With this code, creating a new background process is as simple as running sbcl-daemon <name> <port>.