13

I've done a bit of research on this subject and am turning up blanks. There seem to be implementation-dependent ways of doing Unix signal handling in Common Lisp, but is there a package that gives a cross-implementation way of doing signal handling?

I would mainly like to listen for SIGINT and do a graceful shutdown in my app. I'm using Clozure CL 1.7 on linux...like mentioned, it would be great for a package for this, but if I have to resort to implementation-specific code, that's fine.

I'm also not completely married to using SIGINT (although it's ideal). I can use another signal if needed.

If this is going to be messy, does anyone have any other suggestions for gracefully shutting down a lisp app from outside the app? One idea I had is to create a file the app monitors for, and if it detects the file, it shuts down...kind of hacky, though.

Thanks!

krzysz00
  • 2,083
  • 12
  • 24
andrew
  • 2,819
  • 24
  • 33
  • 1
    Did you try [CFFI](http://common-lisp.net/project/cffi/)? – Daimrod Mar 31 '12 at 06:56
  • No, I didn't try CFFI, I was looking more for a library available via ASDF that would abstract this. I think it may be more trouble than it's worth (for me) to create a cross-implementation unix signal handler in C and write a CFFI wrapper for it...especially since I've never written a C API. I'm not opposed to the idea of doing this, but don't really have the time to do it right now. – andrew Apr 02 '12 at 23:05
  • @Daimrod Your tip got me thinking more. Check out my answer below. At the time I saw your comment, I didn't know it would be possible to deal with unix signals without making an entire wrapper library. You can just call the `signal` C function to replace the signal handler directly from CFFI. – andrew May 04 '12 at 02:58

5 Answers5

9

Although out of ignorance I was originally skeptical of Daimrod's comment (first comment under the question) about using CFFI, I looked around a bit more and found http://clozure.com/pipermail/openmcl-devel/2010-July/011675.html. I adapted it to use CFFI and have confirmed this works on SBCL/CCL/clisp (probably others) on linux pretty damn well:

(defmacro set-signal-handler (signo &body body)
  (let ((handler (gensym "HANDLER")))
    `(progn
       (cffi:defcallback ,handler :void ((signo :int))
         (declare (ignore signo))
         ,@body)
       (cffi:foreign-funcall "signal" :int ,signo :pointer (cffi:callback ,handler)))))

(set-signal-handler 2
  (format t "Quitting lol!!!11~%")
  ;; fictional function that lets the app know to quit cleanly (don't quit from callback)
  (signal-app-to-quit))

Note that from what I understand, whatever is in the body of the callback must be short and sweet! No lengthy processing. In the linked article, the macro actually creates a separate thread just for handling the signal, which is overkill for my purposes, since I'm just setting a global variable from nil to t and returning.

Anyway, hopefully this is helpful to others!

andrew
  • 2,819
  • 24
  • 33
7

This is a late answer, but for anybody else searching for this, have a look at trivial-signal, available on Quicklisp. This is based on CFFI.

Example

(signal-handler-bind ((:int  (lambda (signo)
                               (declare (ignorable signo))
                               ...handler...)))
  ...body...)
coredump
  • 37,664
  • 5
  • 43
  • 77
  • 1
    Thanks, this looks useful. There's some signal handling stuff in cl-async as well, but that only works if you're in an event loop. For general signal handling, this looks like the best option. – andrew May 29 '15 at 17:41
7

I can't find a general library for signal handling either. However, Slime implements "create a custom SIGINT handler" for most Lisp implementations. By looking at the CCL case of that code, I found ccl:*break-hook*. ccl:*break-hook* is not in the documentation, but the commit it was introduced in is located here.

This trivial example code works on my system (CCL 1.8, linux x86):

(setf ccl:*break-hook* 
  (lambda (cond hook)                              
    (declare (ignore cond hook))
    (format t "Cleaning up ...")
    (ccl:quit)))

After this code is entered into a non-Slime REPL, sending SIGINT will cause the program to print "Cleaning up ..." and exit.

krzysz00
  • 2,083
  • 12
  • 24
  • Where is this "create a custom SIGINT handler" feature of Slime? I'm trying apropos on various substrings of that and failing to find it. Oh, hmm, I find `SWANK-BACKEND:INSTALL-SIGINT-HANDLER` in the Slime-specific Apropos, but I'm unsure how to run that. Anyway, if a proper answer of an existing library can't be found, perhaps we could collect these, and figure out what's needed to write one, as a result of this question? :) – lindes Mar 31 '12 at 13:16
  • 1
    `install-sigint-handler` appears to only be implemented for a few implementations (look in `swank-{prog-name}.lisp`). I found `*break-hook*` by looking in the code for ccl's `call-with-debugger-hook`. I'd think that some lisps might not have any mechanism like ccl's `*break-hook*`, so a `handler-case` or `handler-bind` with the name of the condition `#+`ed in would give the most portability. (And yes, the library `trivial-sigint` sounds like a good idea.) – krzysz00 Mar 31 '12 at 14:02
  • When I don't include the ccl::quit, it still fires the break condition after this function runs. Is there a way to cancel this? Basically I want to signal to a few threads "Hey, time to clean up and exit nicely." So is there a way to bind the *break-hook* and then continue execution after it runs automatically? Either way, thanks for finding this, it helps a lot! – andrew Apr 02 '12 at 23:17
  • 1
    You will need to "jump" out of the hook to another part of the program if you want to do that. `throw` / `catch`, a condition, a restart, or some other control transfer mechanism might work. But, then again, the `*break-hook*` might have been invoked in the middle of a syscall, so don't try to do too much before exiting. – krzysz00 Apr 03 '12 at 14:37
1

If you use SBCL, you cannot change the signal mask without causing SBCL to crash. Ask nyef about his tips on how to fix SBCL...

Faré
  • 952
  • 5
  • 4
0

So there is trivial-signal as mentioned. Here's how I catch a C-c in my code:

(handler-case
        (my-app-main-function)
      ;; AFAIK trivial-signal is supposed to handle the implementation differences.
      (#+sbcl sb-sys:interactive-interrupt
        #+ccl  ccl:interrupt-signal-condition
        #+clisp system::simple-interrupt-condition
        #+ecl ext:interactive-interrupt
        #+allegro excl:interrupt-signal
        () (progn
             (format *error-output* "Aborting.~&")
             (exit)))
      (error (c) (format t "Woops, an unknown error occured:~&~a~&" c)))
Ehvince
  • 17,274
  • 7
  • 58
  • 79