Skip to content

Commit 077d9ea

Browse files
authored
Use single executor service instead of timer per stream (#738)
Replace the per output-stream Timer instance with a single scheduled thread pool executor with a single thread. - Removes the need for a proxy object on every print-stream. - Executor service is generally preferred over Timer. - The thread is named so its purpose is apparent.
1 parent 5feebdb commit 077d9ea

File tree

2 files changed

+47
-10
lines changed

2 files changed

+47
-10
lines changed

src/cider/nrepl/middleware/out.clj

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
[cider.nrepl.middleware.util.error-handling :refer [with-safe-transport]])
1414
(:import
1515
[java.io PrintWriter Writer PrintStream OutputStream]
16-
[java.util TimerTask Timer]))
16+
[java.util.concurrent
17+
Executors ScheduledExecutorService ThreadFactory TimeUnit]))
1718

1819
(declare unsubscribe-session)
1920

@@ -93,6 +94,22 @@ Please do not inline; they must not be recomputed at runtime."}
9394
(.flush printer))))
9495
true))
9596

97+
(let [id-counter (atom 0)]
98+
(def ^ScheduledExecutorService flush-executor
99+
"An executor used to run flush on `print-stream` instances.
100+
Using a single thread reduces resource usage, and possibly reduces
101+
the interleaving of output from different streams.
102+
The executor service will ensure there is always a thread available,
103+
should a flush throw an exception and kill a thread.
104+
Thread names are generated with id-counter, to make them unique."
105+
(Executors/newScheduledThreadPool
106+
1
107+
(proxy [ThreadFactory] []
108+
(newThread [^Runnable r]
109+
(doto (Thread.
110+
(str "cider-nrepl-output-flusher-" (swap! id-counter inc)))
111+
(.setDaemon true)))))))
112+
96113
(defn print-stream
97114
"Returns a PrintStream suitable for binding as java.lang.System/out or
98115
java.lang.System/err. All operations are forwarded to all output
@@ -101,18 +118,20 @@ Please do not inline; they must not be recomputed at runtime."}
101118
102119
`printer` is the printer var, either #'clojure.core/*out* or
103120
#'clojure.core/*err*."
104-
[printer]
121+
^PrintStream [printer]
105122
(let [delay 100
106-
print-timer (Timer.)
107-
print-flusher (proxy [TimerTask] []
108-
(run []
109-
(.flush ^Writer @printer)))]
110-
(.scheduleAtFixedRate print-timer print-flusher delay delay)
123+
print-flusher (fn [] (.flush ^Writer @printer))
124+
flush-future (.scheduleWithFixedDelay
125+
flush-executor
126+
print-flusher
127+
delay delay TimeUnit/MILLISECONDS)]
128+
111129
(PrintStream.
112130
(proxy [OutputStream] []
113131
(close []
114-
(.cancel print-flusher)
115-
(.cancel print-timer)
132+
;; cancel, allowing any running flush to finish
133+
;; (false passed as mayInterruptIfRunning argument)
134+
(.cancel flush-future false)
116135
(.flush ^OutputStream this))
117136
(write
118137
([int-or-bytes]

test/clj/cider/nrepl/middleware/out_test.clj

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
(ns cider.nrepl.middleware.out-test
22
(:require
33
[cider.nrepl.middleware.out :as o]
4+
[clojure.string :as str]
45
[clojure.test :refer :all])
56
(:import
6-
[java.io PrintWriter StringWriter]))
7+
[java.io PrintWriter StringWriter]
8+
[java.util.concurrent ThreadPoolExecutor]))
79

810
(defn random-str []
911
(->> #(format "%x" (rand-int 15))
@@ -134,3 +136,19 @@
134136
(is (= "byebye" (.toString out-writer)))
135137
(is (= "bye" (.toString ^StringWriter (message-writers 0))))
136138
(is (= "bye" (.toString ^StringWriter (message-writers 1)))))))))
139+
140+
(deftest print-stream-flush-test
141+
(let [out-writer (StringWriter.)
142+
printer (o/print-stream (volatile! out-writer))]
143+
(.write printer 32)
144+
(loop [i 1000]
145+
(when (and (= "" (.toString out-writer)) (>= 0 i))
146+
(Thread/sleep 1)
147+
(recur (unchecked-dec i))))
148+
(is (re-matches
149+
#"cider-nrepl-output-flusher-\d+"
150+
(-> ^ThreadPoolExecutor o/flush-executor
151+
.getThreadFactory
152+
(.newThread #())
153+
.getName)))
154+
(is (= " " (.toString out-writer)))))

0 commit comments

Comments
 (0)