Unix signal handling in (common) lisp
Asked Answered
D

5

14

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!

Desexualize answered 30/3, 2012 at 21:33 Comment(3)
Did you try CFFI?Hamman
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.Desexualize
@Hamman 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.Desexualize
A
8

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.

Araldo answered 31/3, 2012 at 1:19 Comment(4)
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? :)Taryn
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.)Araldo
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!Desexualize
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.Araldo
D
11

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!

Desexualize answered 4/5, 2012 at 2:54 Comment(0)
A
8

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.

Araldo answered 31/3, 2012 at 1:19 Comment(4)
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? :)Taryn
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.)Araldo
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!Desexualize
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.Araldo
C
8

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...)
Celiaceliac answered 29/5, 2015 at 7:16 Comment(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.Desexualize
C
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...

Clearcut answered 24/8, 2015 at 17:50 Comment(0)
V
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)))
Vein answered 16/3, 2022 at 15:13 Comment(2)
This only handles SIGINT. How do I handle SIGTERM in SBCL?Retarded
With trivial-signal. Examples.Vein

© 2022 - 2024 — McMap. All rights reserved.