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:
parent
bf08da5742
commit
27f0f36030
3 changed files with 26 additions and 18 deletions
2
NEWS
2
NEWS
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue