Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ project's deps are on the source roots and its native libs are loaded, so
Each session keeps its own current namespace and runs evals on a dedicated
serialized worker thread, so sessions are isolated and a long eval doesn't block
other ops. `interrupt` aborts the running eval and the session keeps serving.
An eval's output reaches the client as it is printed, one `out` (or `err`)
message per flush, not all at once when the eval finishes. That needs jolt
0.8.13, the floor `deps.edn` declares.

Leave `cider.nrepl/cider-middleware` out if you only want eval: the cider ops are
what pull in orchard and compliment.
Expand Down
4 changes: 3 additions & 1 deletion deps.edn
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{;; jolt.ffi/write takes the value BEFORE the offset as of jolt 0.8.0, and the
;; two spellings are both integers — an older runtime writes to the wrong
;; place rather than failing. Declare the floor so it refuses instead.
:jolt/min-version "0.8.0"
;; 0.8.13: jolt.nrepl/evaluate's {:out f :err f} arity, which the session
;; middleware streams eval output through.
:jolt/min-version "0.8.13"

:paths ["src"]

Expand Down
7 changes: 6 additions & 1 deletion src/nrepl/middleware/session.clj
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@
ns-atom (:ns session)
;; the actual evaluation runs interruptibly: interrupt! on `token` aborts
;; it (throws {:jolt/interrupted true}), which the worker catches below.
;; Output streams: evaluate calls :out / :err with each chunk as it is
;; flushed, while the eval runs, as upstream's session writers do. :out
;; then comes back empty.
{:keys [value out ns err ex err-msg ex-class root-ex-class]}
(jolt.host/run-interruptible token (fn [] (server/evaluate code (or ns-str @ns-atom))))]
(jolt.host/run-interruptible token (fn [] (server/evaluate code (or ns-str @ns-atom)
{:out (fn [s] (reply {"out" s}))
:err (fn [s] (reply {"err" s}))})))]
(reset! ns-atom ns)
(when (seq out) (reply {"out" out}))
(cond
Expand Down
17 changes: 17 additions & 0 deletions test/nrepl/middleware/session_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,20 @@
c (h/combine (h/eval-code t "(map (fn [x] (println \"side\" x) x) [1 2])" :session s))]
(is (= "side 1\nside 2\n" (:out c)))
(is (= ["(1 2)"] (:value c)))))))

(deftest eval-output-streams-as-it-is-flushed
(with-conn t
(let [s (h/new-session t)
rs (h/eval-code t "(println \"first\") (Thread/sleep 200) (println \"second\") :ok" :session s)]
(testing "each println is its own out message, not one batch at the end"
(is (= ["first\n" "second\n"] (keep :out rs))))
(testing "the output comes before the value"
(is (< (.indexOf ^java.util.List (vec rs) (first (filter :out rs)))
(.indexOf ^java.util.List (vec rs) (first (filter :value rs)))))))))

(deftest eval-err-output-reaches-the-client
(with-conn t
(let [s (h/new-session t)
rs (h/eval-code t "(binding [*out* *err*] (println \"warned\")) :ok" :session s)]
(is (= ["warned\n"] (keep :err rs)))
(is (= [":ok"] (keep :value rs))))))
Loading