Suppress socket closed exceptions in interactive tasks.

This requires heinous hackery! It appears the only way to get out of
clojure.main/repl is by sending an EOF character, but there doesn't
seem to be a way to do that over sockets; you can only close
sockets. Reading from a closed socket results in an exception, so we
rebind clojure.main/skip-whitespace, which is where the reading
happens, to a function that fakes an EOF if it sees an IOException.

We also have to pass in a custom :caught function to suppress
SocketExceptions since the clojure.main/repl will continue to attempt
to perform I/O on the socket even once we are done with it.
This commit is contained in:
Phil Hagelberg 2011-01-24 19:41:33 -08:00
parent bf08da5742
commit 27f0f36030
3 changed files with 26 additions and 18 deletions

2
NEWS
View file

@ -2,6 +2,8 @@ Leiningen NEWS -- history of user-visible changes
= 1.5.0 / ???
* Fix bug in interactive task that would cause infinite loop.
* Add version into shell wrapper template.
* Add pcmpl-lein.el for eshell completion.

View file

@ -55,24 +55,14 @@
(print-prompt)
(recur (.readLine *in*))))))
(def ^{:private true} repl-server-options
[:prompt '(constantly "")
:caught '(fn [t] ; TODO: too broad, probably
(when (instance? java.net.SocketException t)
;; only way to silently exit clojure.main/repl afaict
;; TODO: this causes an infinite loop; can't debug
;; since we don't have access to *err*. grrrrr...
;; (set! *err* (java.io.PrintWriter. (java.io.StringWriter.)))
(throw t)))])
(defn interactive
"Enter an interactive shell for calling tasks without relaunching JVM."
[project]
(let [[port host] (repl-socket-on project)]
(println welcome)
(future
(eval-in-project project `(do ~(apply repl-server project host port
repl-server-options)
(eval-in-project project `(do ~(repl-server project host port
:prompt '(constantly ""))
(symbol ""))))
(let [connect #(poll-repl-connection port 0 vector)]
(binding [eval-in-project (partial eval-in-repl connect)

View file

@ -20,20 +20,36 @@
(in-ns '~'user)))]
repl-options (concat init-form repl-options)]
`(do (try ;; transitive requires don't work for stuff on bootclasspath
(require ['~'clojure.java.shell])
(require ['~'clojure.java.browse])
(require '~'clojure.java.shell)
(require '~'clojure.java.browse)
;; these are new in clojure 1.2, so swallow exceptions for 1.1
(catch Exception _#))
(use ['~'clojure.main :only ['~'repl]])
(let [server# (ServerSocket. ~port 0 (InetAddress/getByName ~host))
acc# (fn [s#]
(let [ins# (.getInputStream s#)
outs# (.getOutputStream s#)]
outs# (.getOutputStream s#)
skip-whitespace# @(ns-resolve '~'clojure.main
'~'skip-whitespace)
;; Suppress socket closed exceptions since
;; they are part of normal operation
caught# (fn [t#]
(when-not (instance? SocketException t#)
(throw t#)))]
(doto (Thread.
#(binding [*in* (-> ins# InputStreamReader.
LineNumberingPushbackReader.)
*out* (OutputStreamWriter. outs#)]
(clojure.main/repl ~@repl-options)))
*out* (OutputStreamWriter. outs#)
*err* *err*
;; clojure.main/repl has no way
;; to exit without signalling EOF,
;; which we can't do with a socket.
clojure.main/skip-whitespace
(fn [s#]
(try (skip-whitespace# s#)
(catch java.io.IOException _#
:stream-end)))]
(clojure.main/repl :caught caught#
~@repl-options)))
.start)))]
(doto (Thread. #(when-not (.isClosed server#)
(try