I can start my web app with a make run
just fine. When I start it with Systemd I can see the initialization steps succeed, I see the lisp *
prompt but instantly after I get this error:
* ;
; compilation unit aborted
; caught 1 fatal ERROR condition
What's going on, what am I missing?
My .service file:
[Unit]
Description=my app
[Service]
WorkingDirectory=/home/vince/projets/myapp/
ExecStart=/usr/bin/make run
Type=simple
Restart=on-failure
My run script does the following:
;; (eval-when (:compile-toplevel :load-toplevel :execute))
(load "myapp.asd")
(unless (ql:quickload "myapp")
(uiop:quit 1))
(handler-case
(progn
(uiop:format! t "-------- start app…") ;; so far so good
(myapp:startapp :port (ignore-errors (parse-integer (uiop:getenv "PORT")))))
(error (c)
(format *error-output* "~&An error occured: ~a~&" c)
;; edit:
(trivial-backtrace:print-backtrace c)))
This starts the Hunchentoot web server.
and I start SBCL with the following switches (not that in makes a difference, with or without the switches it works fine):
LISP ?= /usr/bin/sbcl --core /home/vince/projets/ciel/ciel --disable-debugger --userinit /home/vince/.sbclrc
status:
$ sudo systemctl status myapp.service *[master]
● cmdcollectivites.service - myapp.
Loaded: loaded (/etc/systemd/system/myapp.service; static; vendor preset: enabled)
Active: inactive (dead)
Jun 09 11:22:48 pommier make[31598]: Loading config file...
Jun 09 11:22:48 pommier make[31598]: Starting the web server on port 9999
Jun 09 11:22:48 pommier make[31598]: ✔ Ready. You can access the application!
Jun 09 11:22:48 pommier make[31598]: * ;
Jun 09 11:22:48 pommier make[31598]: ; compilation unit aborted
Jun 09 11:22:48 pommier make[31598]: ; caught 1 fatal ERROR condition
lines 1-14/14 (END)
There's something that Systemd expects but that CL is not giving. What about the Lisp prompt?
SBCL 1.4.5-debian (some issues with the 2.0…)
Using handler-bind
doesn't change the output.
(handler-bind ((error (lambda (c)
(format *error-output* "~&An error occured: ~a~&" c)
(format *error-output* "~&Backtrace: ~&")
(trivial-backtrace:print-backtrace c))))
(progn
(uiop:format! t "-------- start app… --------------")
(cmdcollectivites:startapp :port (ignore-errors (parse-integer (uiop:getenv "PORT"))))))
I got tricked by the *
prompt. I knew what's going on since I wrote about it on StackOverflow: Deploying Common Lisp Web Applications and on the Cookbook: https://lispcookbook.github.io/cl-cookbook/scripting.html#for-web-apps
But in this case I saw this log:
* ;
; compilation unit aborted
; caught 1 fatal ERROR condition
I thought the REPL was waiting for us, and that there was actually an error. Wrong.
The issue was that the Hunchentoot server was started in the background (as always), and nothing was telling the Lisp image to stay with us. It exited right away.
The solution is to put the server thread in the foreground, here using bordeaux-threads
:
(bt:join-thread (find-if (lambda (th)
(search "hunchentoot" (bt:thread-name th)))
(bt:all-threads)))
But still… it isn't clear to me why we get "caught 1 fatal error" with Systemd, why the process stops when it works as expected when run from the terminal, where we have the REPL waiting on the foreground.
Things to keep in mind when starting your app:
you probably rely on Quicklisp. Then on your .sbclrc. Then on QL's snippet which uses (user-homedir-pathname)
. But you use Systemd from root, and you probably didn't install QL in your root home directory. So, adapt this line, or use this sbcl switch:
sbcl --userinit /path/to/your/.sbclrc
the --disable-debugger
switch is good for production (but handy to keep for debugging…)
don't forget you can build a binary, so you won't face these two issues.