From 448cace9feebe89f367b8dfc23dfd138b6bf20cc Mon Sep 17 00:00:00 2001 From: Antonin Hildebrand Date: Mon, 26 Sep 2016 15:20:21 +0200 Subject: [PATCH] nrepl: try to understand interruptible-eval magic --- src/nrepl/dirac/nrepl/piggieback.clj | 40 +++++++++++++++++----------- src/nrepl/dirac/nrepl/sessions.clj | 38 +++++++++++++------------- src/nrepl/dirac/nrepl/state.clj | 2 ++ 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/nrepl/dirac/nrepl/piggieback.clj b/src/nrepl/dirac/nrepl/piggieback.clj index 973cded3f5..cb72fe17bc 100644 --- a/src/nrepl/dirac/nrepl/piggieback.clj +++ b/src/nrepl/dirac/nrepl/piggieback.clj @@ -22,7 +22,7 @@ cljs.repl [cljs.env :as env] [cljs.analyzer :as ana] - [dirac.nrepl.state :refer [*cljs-repl-env* *cljs-compiler-env* *cljs-repl-options* *original-clj-ns*]] + [dirac.nrepl.state :as state] [dirac.nrepl.driver :as driver] [dirac.nrepl.version :refer [version]] [dirac.nrepl.sessions :as sessions] @@ -164,7 +164,7 @@ ; environment now (instead of attempting to create one to ; begin with, because we can't reliably replicate what ; cljs.repl/repl* does in terms of options munging - (set! *cljs-compiler-env* env/*compiler*) + (set! state/*cljs-compiler-env* env/*compiler*) ; if the CLJS evaluated result is nil, then we can assume ; what was evaluated was a cljs.repl special fn (e.g. in-ns, ; require, etc) @@ -203,18 +203,19 @@ Accepts all options usually accepted by e.g. cljs.repl/repl." [repl-env & {:as options}] ; TODO I think we need a var to set! the compiler environment from the REPL environment after each eval + (log/trace "start-cljs-repl! call stack: " (helpers/get-printed-stack-trace)) (try (let [init-code (nrepl/code (ns cljs.user (:require [cljs.repl :refer-macros (source doc find-doc apropos dir pst)]))) nrepl-message (assoc nrepl-ieval/*msg* ::first-cljs-repl true)] ; initial cljs-repl-iteration is hadnled specially (set! ana/*cljs-ns* 'cljs.user) (run-single-cljs-repl-iteration nrepl-message init-code repl-env nil options) ; this will implicitly set! *cljs-compiler-env* - (set! *cljs-repl-env* repl-env) - (set! *cljs-repl-options* options) - (set! *original-clj-ns* *ns*) ; interruptible-eval is in charge of emitting the final :ns response in this context + (set! state/*cljs-repl-env* repl-env) + (set! state/*cljs-repl-options* options) + (set! state/*original-clj-ns* *ns*) ; interruptible-eval is in charge of emitting the final :ns response in this context (set! *ns* (find-ns ana/*cljs-ns*))) (catch Exception e - (set! *cljs-repl-env* nil) + (set! state/*cljs-repl-env* nil) (throw e)))) ;; mostly a copy/paste from interruptible-eval @@ -239,28 +240,29 @@ ; Clojure session (dynamic environment) is not in place, so we need to go ; through the `session` atom to access/update its vars. Same goes for load-file. (defn evaluate! [nrepl-message] + (log/trace "evaluate! call stack: " (helpers/get-printed-stack-trace)) (let [{:keys [session transport ^String code]} nrepl-message] ; we append a :cljs/quit to every chunk of code evaluated so we can break out of cljs.repl/repl*'s loop, ; so we need to go a gnarly little stringy check here to catch any actual user-supplied exit (if-not (.. code trim (endsWith ":cljs/quit")) - (let [repl-env (@session #'*cljs-repl-env*) - compiler-env (@session #'*cljs-compiler-env*) - options (@session #'*cljs-repl-options*)] + (let [repl-env (@session #'state/*cljs-repl-env*) + compiler-env (@session #'state/*cljs-compiler-env*) + options (@session #'state/*cljs-repl-options*)] (run-single-cljs-repl-iteration nrepl-message code repl-env compiler-env options)) - (let [actual-repl-env (@session #'*cljs-repl-env*)] + (let [actual-repl-env (@session #'state/*cljs-repl-env*)] (reset! (:cached-setup actual-repl-env) :tear-down) ; TODO: find a better way (cljs.repl/-tear-down actual-repl-env) (sessions/remove-dirac-session-descriptor! session) (swap! session assoc - #'*ns* (@session #'*original-clj-ns*) - #'*cljs-repl-env* nil - #'*cljs-compiler-env* nil - #'*cljs-repl-options* nil + #'*ns* (@session #'state/*original-clj-ns*) + #'state/*cljs-repl-env* nil + #'state/*cljs-compiler-env* nil + #'state/*cljs-repl-options* nil #'ana/*cljs-ns* 'cljs.user) (transport/send transport (response-for nrepl-message :value "nil" :printed-value 1 - :ns (str (@session #'*original-clj-ns*)))))))) + :ns (str (@session #'state/*original-clj-ns*)))))))) ; struggled for too long trying to interface directly with cljs.repl/load-file, ; so just mocking a "regular" load-file call @@ -559,10 +561,16 @@ (defn dirac-nrepl-middleware [next-handler] (fn [nrepl-message] + ; we are a middleware which is expected to be called in the context of clojure.tools.nrepl.middleware.interruptible-eval + ; interruptible-eval does binding swapping specified by vars in sesssion + ; on first call we install our extra bindings and let interruptible-eval store/restore it for us + ; long story short: with each future invocation our dynamic vars in state namespace will be bound to values relevant to + ; the session at hand + (sessions/install-bindings-if-needed! (:session nrepl-message)) (let [nrepl-message (logged-nrepl-message nrepl-message)] (log/debug "dirac-nrepl-middleware:" (:op nrepl-message) (sessions/get-session-id (:session nrepl-message))) (log/trace "received nrepl message:\n" (pprint nrepl-message)) - (sessions/ensure-bindings! (:session nrepl-message)) + (log/trace "dirac-nrepl-middleware call stack: " (helpers/get-printed-stack-trace)) (cond (dirac-special-command? nrepl-message) (handle-dirac-special-command! nrepl-message) (is-eval-cljs-quit-in-joined-session? nrepl-message) (issue-dirac-special-command! nrepl-message ":disjoin") diff --git a/src/nrepl/dirac/nrepl/sessions.clj b/src/nrepl/dirac/nrepl/sessions.clj index adf024756c..f17f72b5f2 100644 --- a/src/nrepl/dirac/nrepl/sessions.clj +++ b/src/nrepl/dirac/nrepl/sessions.clj @@ -1,12 +1,8 @@ (ns dirac.nrepl.sessions (:require [clojure.tools.logging :as log] - [cljs.analyzer :as ana] + [cljs.analyzer :as analyzer] [dirac.logging :as logging] - [dirac.nrepl.state :refer [session-descriptors]] - [dirac.nrepl.state :refer [*cljs-repl-env* - *cljs-compiler-env* - *cljs-repl-options* - *original-clj-ns*]] + [dirac.nrepl.state :as state] [clojure.tools.nrepl.middleware.interruptible-eval :as nrepl-ieval] [dirac.backport.string :as backport-string])) @@ -16,7 +12,7 @@ (-> session meta :id)) (defn dirac-session? [session] - (boolean (@session #'*cljs-repl-env*))) + (boolean (@session #'state/*cljs-repl-env*))) (defn get-current-session [] {:post [%]} @@ -24,14 +20,16 @@ ; -- bindings magic --------------------------------------------------------------------------------------------------------- -(defn ensure-bindings! [session] - ; ensure that bindings exist so cljs-repl can set! - (if-not (contains? @session #'*cljs-repl-env*) - (swap! session (partial merge {#'*cljs-repl-env* *cljs-repl-env* - #'*cljs-compiler-env* *cljs-compiler-env* - #'*cljs-repl-options* *cljs-repl-options* - #'*original-clj-ns* *original-clj-ns* - #'ana/*cljs-ns* ana/*cljs-ns*})))) +(defn install-bindings-if-needed! [session] + ; this magic here expects interruptible-eval bindings jugggling + ; https://github.com/clojure/tools.nrepl/blob/fcac1e13d6f80eb0db28773247a769c1dc9659b1/src/main/clojure/clojure/tools/nrepl/middleware/interruptible_eval.clj#L85 + (when-not (contains? @session #'state/*cljs-repl-env*) + (log/debug "installing session bindings") + (swap! session (partial merge {#'state/*cljs-repl-env* state/*cljs-repl-env* + #'state/*cljs-compiler-env* state/*cljs-compiler-env* + #'state/*cljs-repl-options* state/*cljs-repl-options* + #'state/*original-clj-ns* state/*original-clj-ns* + #'analyzer/*cljs-ns* analyzer/*cljs-ns*})))) ; -- dirac sessions management ---------------------------------------------------------------------------------------------- @@ -41,7 +39,7 @@ :tag tag}) (defn find-dirac-session-descriptor [session] - (some #(if (= (:session %) session) %) @session-descriptors)) + (some #(if (= (:session %) session) %) @state/session-descriptors)) (defn get-dirac-session-descriptor-transport [session-descriptor] (:transport session-descriptor)) @@ -57,18 +55,18 @@ (log/trace transport (logging/pprint session)) (if-not (find-dirac-session-descriptor session) (let [session-descriptor (make-dirac-session-descriptor session transport tag)] - (swap! session-descriptors concat (list session-descriptor))) + (swap! state/session-descriptors concat (list session-descriptor))) (log/error "attempt to add duplicit session descriptor:\n" (logging/pprint session)))) (defn remove-dirac-session-descriptor! [session] (log/debug "remove-dirac-session-descriptor!" (get-session-id session)) (log/trace (logging/pprint session)) (if-let [session-descriptor (find-dirac-session-descriptor session)] - (swap! session-descriptors #(remove #{session-descriptor} %)) + (swap! state/session-descriptors #(remove #{session-descriptor} %)) (log/error "attempt to remove unknown session descriptor:\n" (logging/pprint session)))) (defn find-matching-dirac-session-descriptors [matcher] - (let [descriptors @session-descriptors + (let [descriptors @state/session-descriptors descriptors-count (count descriptors) match-result (fn [index descriptor] (if (matcher descriptor index descriptors-count) descriptor))] @@ -91,7 +89,7 @@ (prepare-dirac-session-descriptor-tag (find-dirac-session-descriptor session))) (defn get-dirac-session-tags [] - (get-dirac-session-descriptors-tags @session-descriptors)) + (get-dirac-session-descriptors-tags @state/session-descriptors)) ; -- joining sessions ------------------------------------------------------------------------------------------------------- diff --git a/src/nrepl/dirac/nrepl/state.clj b/src/nrepl/dirac/nrepl/state.clj index f7e8d13c9a..919c35b11b 100644 --- a/src/nrepl/dirac/nrepl/state.clj +++ b/src/nrepl/dirac/nrepl/state.clj @@ -4,6 +4,8 @@ ; we cannot pass nrepl-message info into all our functions, ; so we keep some global state around and various functions touch it at will +; also note that these get re-bound with each new nrepl message by clojure.tools.nrepl.middleware.interruptible-eval +; and each new middleware invocation has their own session-specific version (def ^:dynamic *cljs-repl-env* nil) ; this is the var that is checked by the middleware to determine whether an active CLJS REPL is in flight (def ^:dynamic *cljs-compiler-env* nil)