Skip to content

Commit 9b9c934

Browse files
committed
Fix dropped errors in d/timeout!
When putting the result deferred `d` in an error state, deferred returned by `chain` would be detected as a dropped error because it's only used to attach a side-effecting callback for cancelling the timeout. The fix then is to use `on-realized` instead to attach a handler for both cases. As a consequence, timeouts are now also properly cancelled when the result deferred is put in an error state, freeing up resources in a more timely fashion. This fixes one of the causes of clj-commons/aleph#766, as well as 2 of the 3 dropped errors in `manifold.go-off-test`. Also, add dedicated tests for `d/timeout!`.
1 parent 72522ea commit 9b9c934

File tree

2 files changed

+33
-5
lines changed

2 files changed

+33
-5
lines changed

src/manifold/deferred.clj

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,9 +1293,12 @@
12931293
(let [timeout-d (time/in interval
12941294
#(error! d
12951295
(TimeoutException.
1296-
(str "timed out after " interval " milliseconds"))))]
1297-
(chain d (fn [_]
1298-
(success! timeout-d true)))))
1296+
(str "timed out after " interval " milliseconds"))))]
1297+
(on-realized d
1298+
(fn [_]
1299+
(success! timeout-d true))
1300+
(fn [_]
1301+
(success! timeout-d false)))))
12991302
d)
13001303
([d interval timeout-value]
13011304
(cond
@@ -1308,8 +1311,11 @@
13081311
:else
13091312
(let [timeout-d (time/in interval
13101313
#(success! d timeout-value))]
1311-
(chain d (fn [_]
1312-
(success! timeout-d true)))))
1314+
(on-realized d
1315+
(fn [_]
1316+
(success! timeout-d true))
1317+
(fn [_]
1318+
(success! timeout-d false)))))
13131319
d))
13141320

13151321
(deftype+ Recur [s]

test/manifold/deferred_test.clj

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,28 @@
239239
(is (= 1 @d))
240240
(is (= 1 (deref d 10 :foo)))))
241241

242+
(deftest test-timeout
243+
(testing "exception by default"
244+
(let [d (d/deferred)
245+
t (d/timeout! d 1)]
246+
(is (identical? d t))
247+
(is (thrown-with-msg? TimeoutException
248+
#"^timed out after 1 milliseconds$"
249+
@d))))
250+
251+
(testing "custom default value"
252+
(let [d (d/deferred)
253+
t (d/timeout! d 1 ::timeout)]
254+
(is (identical? d t))
255+
(is @(capture-success d ::timeout))))
256+
257+
(testing "error before timeout"
258+
(let [ex (Exception.)
259+
d (d/deferred)
260+
t (d/timeout! d 1000)]
261+
(d/error! d ex)
262+
(is (= ex (deref (capture-error t) 100 ::error))))))
263+
242264
(deftest test-loop
243265
;; body produces a non-deferred value
244266
(is @(capture-success

0 commit comments

Comments
 (0)