Skip to content

Commit 84004ff

Browse files
committed
refactor so that printing and evaluation result has a deterministic
order at the REPL.
1 parent d2d2d08 commit 84004ff

2 files changed

Lines changed: 53 additions & 49 deletions

File tree

src/main/clojure/cljs/repl/node.clj

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@
2121
[java.io File Reader BufferedReader BufferedWriter
2222
InputStreamReader IOException]
2323
[java.lang ProcessBuilder Process]
24-
[java.util.concurrent ConcurrentHashMap]))
24+
[java.util.concurrent ConcurrentHashMap LinkedBlockingQueue]))
2525

2626
(def lock (Object.))
27+
(def results (ConcurrentHashMap.))
2728
(def outs (ConcurrentHashMap.))
2829
(def errs (ConcurrentHashMap.))
2930

31+
(defn thread-name []
32+
(.getName (Thread/currentThread)))
33+
3034
(defn create-socket [^String host port]
3135
(let [socket (Socket. host (int port))
3236
in (io/reader socket)
@@ -56,14 +60,10 @@
5660
(defn node-eval
5761
"Evaluate a JavaScript string in the Node REPL process."
5862
[repl-env js]
59-
(let [{:keys [in out]} @(:socket repl-env)]
60-
;; escape backslash for Node.js under Windows
61-
(write out
62-
(json/write-str
63-
{"repl" (.getName (Thread/currentThread))
64-
"form" js}))
65-
(let [result (json/read-str
66-
(read-response in) :key-fn keyword)]
63+
(let [tname (thread-name)
64+
{:keys [out]} @(:socket repl-env)]
65+
(write out (json/write-str {:type "eval" :repl tname :form js}))
66+
(let [result (.take ^LinkedBlockingQueue (.get results tname))]
6767
(condp = (:status result)
6868
"success"
6969
{:status :success
@@ -88,23 +88,26 @@
8888
(defn- alive? [proc]
8989
(try (.exitValue proc) false (catch IllegalThreadStateException _ true)))
9090

91-
(defn- pipe [^Process proc in stream ios]
91+
(defn- event-loop [^Process proc in]
9292
;; we really do want system-default encoding here
93-
(with-open [^Reader in (-> in InputStreamReader. BufferedReader.)]
94-
(while (alive? proc)
95-
(try
96-
(let [res (read-response in)]
97-
(try
98-
(let [{:keys [repl content]} (json/read-str res :key-fn keyword)
99-
stream (or (.get ios repl) stream)]
100-
(.write stream content 0 (.length ^String content))
101-
(.flush stream))
102-
(catch Throwable _
103-
(.write stream res 0 (.length res))
104-
(.flush stream))))
105-
(catch IOException e
106-
(when (and (alive? proc) (not (.contains (.getMessage e) "Stream closed")))
107-
(.printStackTrace e *err*)))))))
93+
(while (alive? proc)
94+
(try
95+
(let [res (read-response in)]
96+
(try
97+
(let [{:keys [type repl value] :or {repl "main"} :as event}
98+
(json/read-str res :key-fn keyword)]
99+
(case type
100+
"result"
101+
(.offer (.get results repl) event)
102+
(when-let [stream (.get (if (= type "out") outs errs) repl)]
103+
(.write stream value 0 (.length ^String value))
104+
(.flush stream))))
105+
(catch Throwable _
106+
(.write *out* res 0 (.length res))
107+
(.flush *out*))))
108+
(catch IOException e
109+
(when (and (alive? proc) (not (.contains (.getMessage e) "Stream closed")))
110+
(.printStackTrace e *err*))))))
108111

109112
(defn- build-process
110113
[opts repl-env input-src]
@@ -122,22 +125,19 @@
122125
([repl-env] (setup repl-env nil))
123126
([{:keys [host port socket state] :as repl-env} opts]
124127
(let [tname (.getName (Thread/currentThread))]
128+
(.put results tname (LinkedBlockingQueue.))
125129
(.put outs tname *out*)
126130
(.put errs tname *err*))
127131
(locking lock
128132
(when-not @socket
129-
(let [out *out*
130-
err *err*
131-
output-dir (io/file (util/output-directory opts))
133+
(let [output-dir (io/file (util/output-directory opts))
132134
_ (.mkdirs output-dir)
133135
of (io/file output-dir "node_repl.js")
134136
_ (spit of
135137
(string/replace (slurp (io/resource "cljs/repl/node_repl.js"))
136138
"var PORT = 5001;"
137139
(str "var PORT = " (:port repl-env) ";")))
138140
proc (.start (build-process opts repl-env of))
139-
_ (do (.start (Thread. (bound-fn [] (pipe proc (.getInputStream proc) out outs))))
140-
(.start (Thread. (bound-fn [] (pipe proc (.getErrorStream proc) err errs)))))
141141
env (ana/empty-env)
142142
core (io/resource "cljs/core.cljs")
143143
;; represent paths as vectors so we can emit JS arrays, this is to
@@ -157,6 +157,7 @@
157157
(if @socket
158158
(recur (read-response (:in @socket)))
159159
(recur nil))))
160+
(.start (Thread. (bound-fn [] (event-loop proc (:in @socket)))))
160161
;; compile cljs.core & its dependencies, goog/base.js must be available
161162
;; for bootstrap to load, use new closure/compile as it can handle
162163
;; resources in JARs
@@ -235,7 +236,8 @@
235236
(load-javascript this provides url))
236237
(-tear-down [this]
237238
(swap! state update :listeners dec)
238-
(let [tname (Thread/currentThread)]
239+
(let [tname (thread-name)]
240+
(.remove results tname)
239241
(.remove outs tname)
240242
(.remove errs tname))
241243
(locking lock

src/main/clojure/cljs/repl/node_repl.js

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,6 @@ var dom = require("domain").create();
1515
var PORT = 5001;
1616
var repl = null;
1717

18-
process.stdout.write = (function(write) {
19-
return function(chunk, encoding, fd) {
20-
var args = Array.prototype.slice.call(arguments, 0);
21-
args[0] = JSON.stringify({repl: repl, content: chunk});
22-
write.apply(process.stdout, args);
23-
write.call(process.stdout, "\0");
24-
};
25-
})(process.stdout.write);
26-
27-
process.stderr.write = (function(write) {
28-
return function(chunk, encoding, fd) {
29-
var args = Array.prototype.slice.call(arguments, 0);
30-
args[0] = JSON.stringify({repl: repl, content: chunk});
31-
write.apply(process.stderr, args);
32-
write.call(process.stderr, "\0");
33-
};
34-
})(process.stderr.write);
35-
3618
try {
3719
require("source-map-support").install();
3820
} catch(err) {
@@ -48,6 +30,23 @@ var server = net.createServer(function (socket) {
4830

4931
socket.setEncoding("utf8");
5032

33+
process.stdout.write = function(chunk, encoding, fd) {
34+
var args = Array.prototype.slice.call(arguments, 0);
35+
args[0] = JSON.stringify({type: "out", repl: repl, value: chunk});
36+
socket.write.apply(socket, args);
37+
socket.write("\0");
38+
};
39+
40+
process.stderr.write = (function(write) {
41+
return function(chunk, encoding, fd) {
42+
var args = Array.prototype.slice.call(arguments, 0);
43+
args[0] = JSON.stringify({type: "err", repl: repl, value: chunk});
44+
socket.write.apply(socket, args);
45+
socket.write("\0");
46+
};
47+
})(process.stderr.write);
48+
49+
5150
dom.on("error", function(ue) {
5251
console.error(ue.stack);
5352
});
@@ -84,18 +83,21 @@ var server = net.createServer(function (socket) {
8483

8584
if(err) {
8685
socket.write(JSON.stringify({
86+
type: "result",
8787
repl: repl,
8888
status: "exception",
8989
value: err.stack
9090
}));
9191
} else if(ret !== undefined && ret !== null) {
9292
socket.write(JSON.stringify({
93+
type: "result",
9394
repl: repl,
9495
status: "success",
9596
value: ret.toString()
9697
}));
9798
} else {
9899
socket.write(JSON.stringify({
100+
type: "result",
99101
repl: repl,
100102
status: "success",
101103
value: null

0 commit comments

Comments
 (0)