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

Reply via email to