[slime-devel] Re: OpenMCL

Luke Gorrie luke at bluetail.com
Sat Dec 13 02:52:04 UTC 2003


Peter Seibel <peter at javamonkey.com> writes:

> Here's how I was envisioning it: when I pick a thread to debug from
> the list of debuggable therads SLIME creates a new buffer for
> interacting with that thread. I switch between threads by switching
> between buffers.  Thus I can C-x 2 to split my window and put one
> thread-buffer in each window and switch back and forth with C-x
> o.

The main issue is for people writing SLIME-based Elisp code. Anyone
not interested in how that works can safely skip this explanation :-)

The tricky part is when Emacs has made RPCs to Lisp, and the Lisp
threads being debugged are actually computing results to return to
Emacs functions.

Initially the Emacs stack looks something like:

  ((emacs-event-loop))

Then we call an Emacs function that wants to RPC to Lisp to evaluate
the expression FOO in thread T1 and do-something-with the result (the
top of the stack comes first):

  ((receive-reply-from T1)
   (do-something-with (eval-in-thread FOO T1))
   (emacs-event-loop))

But FOO causes an error and invokes the debugger, so Emacs enters a
recursive edit (i.e. calls the event loop recursively) to do the
debugging, hoping to debug the problem and then get its result:

  ((emacs-event-loop)
   (debug-thread T1)
   (receive-reply-from T1)
   (do-something-with (eval-in-thread FOO T1))
   (emacs-event-loop))

Now we have one thread available for debugging. But suppose we let it
wait a while, and now ask another thread T2 to evaluate an expression
BAR and do-something-else with that result, and T2 also enters the
debugger:

  ((emacs-event-loop)
   (debug-thread T2)
   (do-something-else-with (eval-in-thread BAR T2))
   (emacs-event-loop)
   (debug-thread T1)
   (receive-reply-from T1)
   (do-something-with (eval-in-thread FOO T1))
   (emacs-event-loop))

Now we have two Emacs functions on the stack that are expecting a
value - `do-something-with' and `do-something-else-with'.

See the problem?

To deliver the result of FOO to do-something-with, we need to have
popped everything below off the stack, including
do-something-else-with. So either we have to debug BAR first, so that
it can deliver its result and unwind the stack for us, or we get the
result of FOO and 'throw' up the stack, so that
do-something-else-with is discarded and there won't be anything left
to use the result of BAR.

Okay, so maybe this is not so exciting to you guys doing pure CL, but
it's a big deal for those of us using Emacs as a UI toolkit, and
indirectly for people using the UIs we write :-)

There are some solutions available. One is to just do the throw -- we
have to be able to handle that anyway (and we can) because the same
thing happens if the user calls `abort-recursive-edit'
(`C-]'). Alternatively, we could ditch our synchronous-eval elisp
funtion and use a purely asynchronous interface based on
continuation-passing-style -- then we don't need the Elisp stack or
recursive edits, and can deliver results in any order.

But I don't think the time is ripe yet :-)

BTW, Distel has exactly the debugging interface you describe. All its
Elisp code is totally asynchronous, based on (macro-assisted) CPS with
a trampolined-style scheduler providing a multiprocessing abstraction
in Elisp. Make me happy, read all about it :-)

  Distel: Distributed Emacs Lisp (for Erlang)
  http://www.bluetail.com/~luke/distel/distel-euc.pdf

Cheers,
Luke






More information about the slime-devel mailing list