From 27f0f36030722ac09b246fc0a12fc5d8ace81750 Mon Sep 17 00:00:00 2001 From: Phil Hagelberg Date: Mon, 24 Jan 2011 19:41:33 -0800 Subject: [PATCH] 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. --- NEWS | 2 ++ src/leiningen/interactive.clj | 14 ++------------ src/leiningen/repl.clj | 28 ++++++++++++++++++++++------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/NEWS b/NEWS index 39138fa9..516f2805 100644 --- a/NEWS +++ b/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. diff --git a/src/leiningen/interactive.clj b/src/leiningen/interactive.clj index 6026fa35..10df1b23 100644 --- a/src/leiningen/interactive.clj +++ b/src/leiningen/interactive.clj @@ -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) diff --git a/src/leiningen/repl.clj b/src/leiningen/repl.clj index c70674e6..f17cb29e 100644 --- a/src/leiningen/repl.clj +++ b/src/leiningen/repl.clj @@ -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