On Sat, Dec 18, 2010 at 11:43 AM, Stuart Sierra <the.stuart.sie...@gmail.com> wrote: > You don't necessarily need to kill the whole JVM. If your REPL > implementation runs each command in a new Thread (as most of them do, I > think) it can just stop the thread. That won't work in every situation (for > example, a thread blocked waiting for I/O) but it will get you out of an > infinite sequence.
The two commonest causes of repl hangs are * Buggy (loop ... recur) that does not terminate * Repl tries to print an infinite lazy seq The latter is easy to fix: provide a version of println that wraps an implicit (take n ...) around seq arguments (including when it calls itself on seqs nested within other structures). (*print-length* doesn't seem to work, just causes an infinite seq to print the first n items and then a never-ending string of "..."s.) The former is a bit trickier, but with a debugging flag it could be done. When the flag is set, every (recur ...) form would compile into a check for an interrupted flag FOLLOWED by whatever the (recur ...) would normally produce. If the interrupted flag was set an exception would be thrown. This could be as simple as checking Thread/interrupted and throwing InterruptedException, the existing Java mechanism for this sort of thing. With the flag not set, (recur ...) forms compile as they do now. Then the repl only needs a way to interrupt the thread with a keystroke like control-C. The debug flag could also add interrupted checks to println, doseq, and doall to provide the ability to escape from most situations attempting to realize an infinite seq. This could be implemented by adding one new special form, (debuginterrupt), that does the interruption-check-and-throw and is omitted in non-debug compiles, inserting it in key places in functions like doseq and doall, and making recur a macro instead of a special form; it would call a recur* special form that was the old recur, so (defmacro recur [& stuff] `(do (debuginterrupt) (recur* ~...@stuff))) or similar. The repl would need to be implemented appropriately, though. Instead of the interaction and repl-issued commands running on the same thread the repl would have to spawn a separate thread for a submitted expression and then not accept inputs other than control-C until that thread returned. A GUI repl can handle this easily enough, using SwingWorker to launch the expression and letting the EDT catch both the SwingWorker being done and a control-C keystroke. A command line repl will have to roll its own EDT analogue for this. Control-C sends interrupt to the thread, and the usual result is probably an InterruptedException stack trace printing out and then a new repl prompt. The stack trace might be useful. Letting the user debug state and the like is trickier but (debuginterrupt) provides a suitable hook. Who says the only thing it can do on interrupt is throw an exception? It could also capture &env and launch a GUI inspector or whatever. A mechanism could be provided for hooking on-interrupt behavior into this special form, with the default being (throw InterruptedException). In fact if there is a debug-vs.-production flag known at macroexpansion time, (debuginterrupt) can be a mere macro, something like (def interrupt-handler (atom (fn [_] (throw InterruptedException)))) (defmacro debuginterrupt [] (if not-debug `(if (Thread/interrupted) (@interrupt-handler &env)))) (defmacro set-interrupt-handler [env & body] `(reset! interrupt-handler (fn [~env] ~...@body))) -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en