Interrupting repl in Slime

Alan Ruttenberg alanruttenberg at gmail.com
Wed Apr 6 22:13:04 UTC 2022


Hi folks,

Maybe someone has a feel for how interrupts work in ABCL?

Doing an interrupt in ABCL from Slime doesn't interrupt until the end of a
function or sleep or wait is called.  This means whenever I accidentally
have a too long or infinite loop by mistake the only thing I can do is quit
lisp and start over. This is probably THE most irritating problem I have
with ABCL. It doesn't happen often but when it does it completely disrupts
work.

Slime handles an interrupt by calling interrupt-thread. Interrupt-thread
saves the function and sets a flag. When the interrupted process next
checks the flag it will call the interrupt function. At first I thought
lisp wasn't checking for an interrupt often enough, but that's not the
problem. If I compile (loop for i = 1)  I get

  public final LispObject execute() {
;         do {
;             if (!Lisp.interrupted) {
;                 continue;
;             }
;             Lisp.handleInterrupt();
;         } while (true);
;     }

So why doesn't the interrupt work? Well it seems there are two paths to
interrupt. One is via interrupt-thread. Here is the code that sets the flag.

    final synchronized void interrupt(LispObject function, LispObject args)
    {
        pending = new Cons(args, pending);
        pending = new Cons(function, pending);
        threadInterrupted = true;
        javaThread.interrupt();
    }

Note that it is setting thread.threadInterrupted rather than
Lisp.interrupted. In fact, the only way to set Lisp.interrupted is via
Lisp.setInterrupted() and the only function that calls that is
ext:interrupt-lisp. Nothing calls ext:interrupt-lisp. So that nice check
within the loop is checking for a signal that currently never arrives.

The javathread.interrupt is only periodically detected by an explicit call
or via an InterruptedException that Java will throw when the thread yields
or via a check of thread.isInterrupted. The latter is checked in one place,
inside a catch, which afaik isn't executed unless there's an error in the
thread - it's the "Ignoring uncaught exception" path.

Now, if I call ext:interrupt-lisp from the *inferior-lisp* a break comes up
during the infinite loop and I can kill it! This is the desired behavior.

The problem is, I can't figure out where to call (ext:interrupt-lisp)
within slime. I tried to put it right after interrupt-thread when that is
called in swank::queue-thread-interrupt, which is what is eventually called
when you hit C-c in the repl.  But that causes an error and kills the slime
session. And emacs, for that matter, which needs to be force quit.

I'm thinking that in the case of a control-c we should not use
thread-interrupt but rather lisp interrupted. threadInterrupted is thread
local and Lisp.interrupted is a global. So technically the control c might
not go to the right thread, since all threads are checking the global. But
in the situation where it's locked in an infinite loop, there's no other
thread doing anything, so the right thing happens. In any case I think
that's probably easy to fix by setting another volatile to the thread to be
interrupted. In general one would have to worry about a race condition if
there were several places that might call interrupt-lisp, but in this case
it would only be called with an explicit Control-c and perhaps on a request
to kill a thread from the slime threads buffer.

It's an open question as to whether interrupt-thread should take advantage
of this as well, but that would require more care to avoid a race condition.

Any help on sorting this would be much appreciated.

Thanks,
Alan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/armedbear-devel/attachments/20220406/109f95e9/attachment.html>


More information about the armedbear-devel mailing list