Peter Seibel peter@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