Looks like macroexpand and other uses of eval-string don't read in the right package.
BTW, I've run into a limitation of the current multiprocessing scheme. E.g. if you have a single repl that is busy then you can't meta-point or macroexpand. Was the creation of a dedicated control stream considered and rejected?
It would be useful to have a guaranteed connection from emacs to the lisp - another use would be to have the control stream use process-interrupt instead of relying on sigint getting to the correct thread.
-Alan
Alan Ruttenberg alanralanr@comcast.net writes:
BTW, I've run into a limitation of the current multiprocessing scheme. E.g. if you have a single repl that is busy then you can't meta-point or macroexpand. Was the creation of a dedicated control stream considered and rejected?
It was pushed onto the To-Think list :-)
I'm in agreement. Possibly we should go even further and have Emacs only interact directly with one thread, and have it farm out the work as needed (possibly without Emacs knowing/caring how) and do multiplexing through a single socket.
Do you want to take a turn at redoing it?
Reluctantly I have to take a break from the fun to do some boring stuff (vacation), so I'll be away from the end of next week until mid-March. Meanwhile I have time to discuss but not code another threads design :-)
One thought is this: currently our state machine expects orderliness from Lisp - we send one request at a time, except an answer, etc. To accomodate multithreading we added one similarly orderly connection for each thread.
Maybe a better idea would have been to go for chaos? We send N requests to Lisp at a time, and it replies in any order (with an ID tag to identify the result), we can get multiple unknown threads hitting the debugger at the same time, etc.
I don't have details worked out, but it if this could be made to work it would seem quite flexible. The existing scheme doesn't seem to fit well with options like thread-per-request -- too many sockets and generally Emacs having to know too much about what threads Lisp has.
(With a design nod to Peter Siebel and Brian Downing.)
Cheers, Luke
Luke Gorrie luke@bluetail.com writes:
Maybe a better idea would have been to go for chaos? We send N requests to Lisp at a time, and it replies in any order (with an ID tag to identify the result), we can get multiple unknown threads hitting the debugger at the same time, etc.
I tried to remove the state machine some time ago, but wasn't very successful. The id argument in eval-string stems from that attempt.
Here some problems I encountered:
The current state stack contains (state-name . variables) pairs. It wasn't obvious to me where I should store the variables instead. Including the variables in the request itself doesn't work, because the variables contain things like window configurations. Storing them in a buffer -- e.g., the variables of the debugging state in the sldb buffer -- doesn't work because the user can delete the buffer. So I stored them on a stack again :-)
Another problem was that there where no 'activate' events anymore. When you exited from the debugger at level 3 to level 2, Emacs didn't notice it. My idea was to generate the activate events at the Lisp side. So I changed the Lisp sldb-loop and sent an 'activate-debugger' event every time before reading the next request. The sldb-loop looked like this:
(send-to-emacs '(:initialize-debugger ...)) (loop (send-to-emacs '(:activate-debugger ...)) (read-from-emacs))
This too was problematic, because the :initialize-debugger event could cause a RPC from Emacs to Lisp (e.g., fetching the backtrace). But the result of the RPC arrived after the :activate-event. I gave at this point.
I think we cannot blindly accept every event at any time. Maybe we need something like a mailbox with Erlang-style selective receive :-)
Helmut.
Helmut Eller e9626484@stud3.tuwien.ac.at writes:
I tried to remove the state machine some time ago, but wasn't very successful. The id argument in eval-string stems from that attempt.
I wondered why you put it there! Today I have been working on removing the state machine too, and the `id' is really helpful :-)
Here some problems I encountered:
The current state stack contains (state-name . variables) pairs. It wasn't obvious to me where I should store the variables instead. Including the variables in the request itself doesn't work, because the variables contain things like window configurations. Storing them in a buffer -- e.g., the variables of the debugging state in the sldb buffer -- doesn't work because the user can delete the buffer. So I stored them on a stack again :-)
I've tried putting the sldb window configuration in the *sldb* buffer. It gets set when the SLDB buffer is created and restored when sldb-level 1 exists. If the user kills the buffer, well, he can restore his own stinkin' window configuration ;-)
Another problem was that there where no 'activate' events anymore. When you exited from the debugger at level 3 to level 2, Emacs didn't notice it.
Now that you mention it, I have this problem. :-)
I'll play around a bit more and see what it comes to. Currently I have minimal support for evaluating/debugging/read-string replacing the whole FSM business with about half a page of code:
(defun slime-dispatch-event (event &optional process) (let ((slime-dispatching-connection (or process (slime-connection)))) (slime-log-event event) (unless (slime-handle-oob event) (destructure-case event ((:emacs-rex form-string package continuation) (let ((id (incf slime-continuation-counter))) (push (cons id continuation) slime-rex-continuations) ;; (slime-send `(:rex ,form-string ,package))) (slime-send `(swank:eval-string ,form-string ,package ,id)))) ((:return value id) (when-let (rec (find id slime-rex-continuations :key #'car)) (setq slime-rex-continuations (remove rec slime-rex-continuations)) (let ((continuation (cdr rec))) (funcall continuation value)))) ((:read-string tag) (setq slime-read-string-tag tag)) ((:read-aborted) (setq slime-read-string-tag nil)) ((:emacs-return-string string) (slime-send `(swank:take-input ,slime-read-string-tag ,string)) (setq slime-read-string-tag nil)) ((:debug level condition restarts frames) (sldb-setup level condition restarts frames)) ((:debug-return level) (sldb-exit level))))))
That feels good. If it can be made to work then it also means the code for auxiliary connections in both Emacs and Lisp can be deleted too.
Oh I hope it can be made to work well... :-)
Cheers, Luke
Helmut Eller e9626484@stud3.tuwien.ac.at writes:
The sldb-loop looked like this:
(send-to-emacs '(:initialize-debugger ...)) (loop (send-to-emacs '(:activate-debugger ...)) (read-from-emacs))
This too was problematic, because the :initialize-debugger event could cause a RPC from Emacs to Lisp (e.g., fetching the backtrace). But the result of the RPC arrived after the :activate-event. I gave at this point.
How about making it look like:
(loop (send-to-emacs `(:debug ...)) (read-from-emacs))
i.e. just like today except with the :DEBUG message moved into the loop. This feels okay so far (after 5 minutes..)
I think we cannot blindly accept every event at any time. Maybe we need something like a mailbox with Erlang-style selective receive :-)
Do you have more bad cases in mind already? I wanna wanna wanna make this work, it could solve all our interrupt/thread/etc woes.
I've attached my currently hacked patch just for your info / trouble spotting :-)
Luke Gorrie luke@bluetail.com writes:
How about making it look like:
(loop (send-to-emacs `(:debug ...)) (read-from-emacs))
i.e. just like today except with the :DEBUG message moved into the loop. This feels okay so far (after 5 minutes..)
Oops.. now I see why you split activate and initialize.
Luke Gorrie luke@bluetail.com writes:
Oops.. now I see why you split activate and initialize.
Next incarnation. Pushes the "do we need to reinit *sldb*?" logic from Emacs into Lisp:
(defvar *sldb-level-in-emacs* nil)
(defun sldb-loop (level) (flet ((activate-emacs () (send-to-emacs (list* :debug *sldb-level* (debugger-info-for-emacs 0 *sldb-initial-frames*))) (setq *sldb-level-in-emacs* level))) (activate-emacs) (unwind-protect (loop (catch 'sldb-loop-catcher (with-simple-restart (abort "Return to sldb level ~D." level) (unless (eql level *sldb-level-in-emacs*) (activate-emacs)) (handler-bind ((sldb-condition #'handle-sldb-condition)) (read-from-emacs))))) (send-to-emacs `(:debug-return ,level)))))
I'll continue trying to beat it into submission, and try to properly handle e.g. user killing *sldb* at an inconvenient time, tomorrow.
Luke Gorrie luke@bluetail.com writes:
Do you have more bad cases in mind already? I wanna wanna wanna make this work, it could solve all our interrupt/thread/etc woes.
A single variable for slime-read-string-tag could cause some trouble. Reading from the REPL can be invoked recursively, because the user can evaluate arbitrary code, e.g, with C-c C-:.
I've attached my currently hacked patch just for your info / trouble spotting :-)
Lots of code deleted! That's good :-)
Helmut.
Helmut Eller e9626484@stud3.tuwien.ac.at writes:
A single variable for slime-read-string-tag could cause some trouble. Reading from the REPL can be invoked recursively, because the user can evaluate arbitrary code, e.g, with C-c C-:.
True, thanks.
I've been working on getting it through the test suite. I'm down to two failing tests, but there are other missing things (like this read-string bit).
For read-string maybe the solution is for Emacs to just send (repl-input <string>) and let Lisp decide what to do with it.
I also found an exciting new class of dastardly hard bug :-) example:
Make async request Make sync request A Async reply arrives, in continuation: Make sync request B Result of A arrives and is thrown up the stack past B's handler
Can involve a lot of "what happened to A?" head-scratching :-)
With the old protocol we didn't have this problem since it was illegal to make two requests in a row. On the other hand, this wasn't as hard to debug as an `activate'-related bug in the state machine once, and I _think_ it can be solved by the rule "don't make synchronous requests from asynchronous continuations".
Currently the "interesting" bit is figuring out what slime-sync, slime-busy-p, etc are supposed to do, or how to change their callers. This is the chance to make it compatible with out-of-order (e.g. thread-per-request) operation.
Lots of code deleted! That's good :-)
Let's hope it can stay deleted :-) will see how far I can get tonight.
Current patch attached. It contains "more than a couple" of debug-printfs, mostly as a relic of chasing the aforementioned bug :-)
Luke Gorrie luke@bluetail.com writes:
For read-string maybe the solution is for Emacs to just send (repl-input <string>) and let Lisp decide what to do with it.
Yes, that's probably good enough.
I also found an exciting new class of dastardly hard bug :-) example:
Make async request Make sync request A Async reply arrives, in continuation: Make sync request B Result of A arrives and is thrown up the stack past B's handler
Can involve a lot of "what happened to A?" head-scratching :-)
With the old protocol we didn't have this problem since it was illegal to make two requests in a row. On the other hand, this wasn't as hard to debug as an `activate'-related bug in the state machine once, and I _think_ it can be solved by the rule "don't make synchronous requests from asynchronous continuations".
How about this rule: "RPCs should be properly nested"? So a sequence
request A request B reply A reply B
would be illegal. This would be easy to check (with a stack) and is probably a requirement for synchronous RPCs. The downside of this rule is, is that it looks a lot like what we had before :-(
Currently the "interesting" bit is figuring out what slime-sync, slime-busy-p, etc are supposed to do, or how to change their callers. This is the chance to make it compatible with out-of-order (e.g. thread-per-request) operation.
Have you any plans for how to debug in the thread-per-request world?
Helmut.
Helmut Eller e9626484@stud3.tuwien.ac.at writes:
How about this rule: "RPCs should be properly nested"? So a sequence
request A request B reply A reply B
would be illegal. This would be easy to check (with a stack) and is probably a requirement for synchronous RPCs. The downside of this rule is, is that it looks a lot like what we had before :-(
Some comforting observations:
Synchronous RPCs mostly take care of this themselves by blocking Emacs. The only way to make two is if the outer one breaks into a recursive edit, which will probably happen due to needing to be debugged, where it's probably safe to make inner synchronous RPCs.
Synchronous commands always have to be able to cope with abort because of C-g. So if we just abort them when the stack returns out of order, nothing really bad should happen, the command just gets aborted.
I'm guessing we can program around the problems pretty easily. But you probably noticed the ol' PowerPoint trick of using a bullet-list for an argument that I can't express convincingly in prose :-)
Allowing "pipelining" multiple requests to Lisp and accepting results out-of-order would open a lot of flexibility for threading. For example with thread-per-request you could do `C-c C-k' and still use M-. and so on while you wait for the compilation. I *totally* missed that idea in my previous multithreading attempts but now I suspect it's the most important bit.
Have you any plans for how to debug in the thread-per-request world?
Some ideas at least. We extend the (:DEBUG ..) message with a THREAD-ID so that we can give threads their own *sldb* buffers. We also extend RPCs from Emacs to include a THREAD-ID so that the *sldb* buffers can talk to the right thread (and so the REPL can always talk to the same thread, and maybe commands in buffers could use T to mean "in a new thread" perhaps).
I think that's actually enough for the machinery, since now the debugger state only exists in Lisp stacks (per thread) and the *sldb* buffer-locals (per thread). We could then build a user interface on top that decides when debug buffers should actually be popped up etc.
Or something :-)
Ahoy,
I have checked in my majorly hacked version on a new branch called `stateless-emacs'. If anyone wants to take it for a spin that would be helpful for flushing out problems not covered by the test suite. There is a STATELESS-EMACS tag set at the point where I branched.
Multiprocessing doesn't work in this version because I deleted the auxiliary-connection code from the elisp side and haven't replaced it with any other mechanism.
I'm about to shift into "last minute stuff" mode before buggering off on vacation so I don't think I'll have a chance to hack this significantly more. I put it on a branch in case you guys decide to hack/merge it, otherwise I'll brutalise it some more when I get back.
I'll be gone from the end of the week (start of feb), and I'll be back (with a hacking vengeance!) mid-March or so.
Cheers, Luke
Luke Gorrie luke@bluetail.com writes:
Ahoy,
I have checked in my majorly hacked version on a new branch called `stateless-emacs'. If anyone wants to take it for a spin that would be helpful for flushing out problems not covered by the test suite. There is a STATELESS-EMACS tag set at the point where I branched.
I'm working on this version and changed the CMUCL backend to use signal driven IO. This means that M-. works now even during a compilation. It's even possible to compile when CMUCL is already compiling :-), but it shouldn't be too difficult to disable unwanted nested evaluations. The advantage of signal driven IO is, that the Lisp side can respond to requests at any moment. For instance, interrupting is now more or less a normal message and we don't need Unix signals for IPC. This should also work on remote hosts.
I'm now adding similar functionality for mutlithreaded Lisps. My plan looks like this: we have 3 communicating threads, a "reader", a "control" thread and a "worker" thread. The "reader" reads request from the socket and sends them to the "control" thread. The control thread dispatches requests to worker threads and handles interrupt requests. Currently I create a new worker thread for most requests, but interrupt and debugging messages are sent to the appropriate worker thread. A worker thread executes the request and sends the result back to the controller and the controller sends the result to Emacs. A worker only communicates with the controller and doesn't write directly to the socket. Many messages include now a thread field, so that Emacs knows which thread to talk to.
The thread-per-request support is implemented for SBCL, Lispworks, and Allegro. The SBCL version doesn't work very well, apparently some deadlock problem. Will probably switch to signal driven IO for SBCL. An OpenMCL port should be easy. Haven't decided yet what to do with CLISP. I tried to add signal driven IO for CLISP, but SIGIO is blocked in the SIGIO handler and I get segfaults when I set the SA_NODEFER flag.
I'm also thinking about a possibility to query the state of the Lisp side. The idea is that Emacs receives a list describing the interesting bits of Lisp's current call stack. That would be very useful for writing test cases and perhaps something like a resync command. It would be nice if Lisp could answer such 'get-current-state' requests immediately; it should be treated like an interrupt request.
My question now is: can we assume that Lisp can answer such requests immediately?
It's probably difficult to implement this for CLISP, but it would reduce the complexity of the Emacs side.
Helmut.
Luke Gorrie luke@bluetail.com writes:
I have checked in my majorly hacked version on a new branch called `stateless-emacs'. If anyone wants to take it for a spin that would be helpful for flushing out problems not covered by the test suite. There is a STATELESS-EMACS tag set at the point where I branched.
I've merged this branch into the main trunk. The major user visible change is that M-. remains usable, even when Lisp performs a lengthy computation. This is a major change in the protocol, so be prepared for some new bugs :-)
Helmut.
Helmut Eller e9626484@stud3.tuwien.ac.at writes:
I've merged this branch into the main trunk. The major user visible change is that M-. remains usable, even when Lisp performs a lengthy computation. This is a major change in the protocol, so be prepared for some new bugs :-)
Very very very cool :-)
Helmut Eller e9626484@stud3.tuwien.ac.at writes:
I've merged this branch into the main trunk. The major user visible change is that M-. remains usable, even when Lisp performs a lengthy computation. This is a major change in the protocol, so be prepared for some new bugs :-)
aye. Ctrl-C in CMUCL no longer lands me in the sldb.
but you've probably already seen this. :)
Michael Livshin usenet@cmm.kakpryg.net writes:
aye. Ctrl-C in CMUCL no longer lands me in the sldb.
but you've probably already seen this. :)
No I didn't. Thanks for the note. Should be fixed in CVS.
Helmut.
Alan Ruttenberg alanralanr@comcast.net writes:
Looks like macroexpand and other uses of eval-string don't read in the right package.
eval-string and our macroexpand functions use from-string for reading. from-string reads in the *buffer-package*. Looks right to me.
Or are you suggesting that eval-string should bind *package* to *buffer-package* before evaluating ? That's probably a similar issue as with *load-pathname*. I think it is better not to mess around with user visible dynamic variables. But again, if you think otherwise, just change the code and let us see how that works.
Helmut.
The issue that triggered the note was when I macroexpanded a defstruct form. In that case the accessors were being defined in the wrong package. So it looks like I misdiagnosed the issue - I think it must be that *package* isn't bound. Since I was also evaluating these defstructs I was also confronted with the rather confusing(amusing only in retrospect) situation of no longer having defstruct accessors after I did so. I'll make the change.
What's your experience that makes you worry about binding these kinds of variables?
-Alan
On Jan 24, 2004, at 3:18 AM, Helmut Eller wrote:
Alan Ruttenberg alanralanr@comcast.net writes:
Looks like macroexpand and other uses of eval-string don't read in the right package.
eval-string and our macroexpand functions use from-string for reading. from-string reads in the *buffer-package*. Looks right to me.
Or are you suggesting that eval-string should bind *package* to *buffer-package* before evaluating ? That's probably a similar issue as with *load-pathname*. I think it is better not to mess around with user visible dynamic variables. But again, if you think otherwise, just change the code and let us see how that works.
Helmut.
Alan Ruttenberg alanralanr@comcast.net writes:
What's your experience that makes you worry about binding these kinds of variables?
Nothing in particular. Just my gut feeling.
Helmut.