Skip to content

Commit 8a18e1e

Browse files
committed
Requests placed by server can be treated as promesa promises
1 parent 34fe65d commit 8a18e1e

File tree

3 files changed

+68
-30
lines changed

3 files changed

+68
-30
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Server requests can be treated as promesa promises.
6+
57
## v1.8.1
68

79
- Bump promesa to `10.0.571`
@@ -16,6 +18,8 @@
1618

1719
## v1.7.3
1820

21+
- Server continues receiving responses while it is blocking requests or notifications.
22+
1923
## v1.7.2
2024

2125
## v1.7.1

src/lsp4clj/server.clj

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
[lsp4clj.protocols.endpoint :as protocols.endpoint]
1010
[lsp4clj.trace :as trace]
1111
[promesa.core :as p]
12-
[promesa.protocols])
12+
[promesa.protocols :as p.protocols])
1313
(:import
1414
(java.util.concurrent CancellationException)))
1515

@@ -31,44 +31,39 @@
3131
(defprotocol IBlockingDerefOrCancel
3232
(deref-or-cancel [this timeout-ms timeout-val]))
3333

34-
(defrecord PendingRequest [p cancelled? id method started server]
34+
(defrecord PendingRequest [p id method started server]
3535
clojure.lang.IDeref
3636
(deref [_] (deref p))
3737
clojure.lang.IBlockingDeref
3838
(deref [_ timeout-ms timeout-val]
3939
(deref p timeout-ms timeout-val))
4040
IBlockingDerefOrCancel
4141
(deref-or-cancel [this timeout-ms timeout-val]
42-
(let [result (deref this timeout-ms ::timeout)]
43-
(if (= ::timeout result)
42+
(let [result (deref p timeout-ms ::timeout)]
43+
(if (identical? ::timeout result)
4444
(do (future-cancel this)
4545
timeout-val)
4646
result)))
4747
clojure.lang.IPending
48-
(isRealized [_] (realized? p))
48+
(isRealized [_] (p/done? p))
4949
java.util.concurrent.Future
50-
(get [this]
51-
(let [result (deref this)]
52-
(if (= ::cancelled result)
53-
(throw (java.util.concurrent.CancellationException.))
50+
(get [_] (deref p))
51+
(get [_ timeout unit]
52+
(let [result (deref p (.toMillis unit timeout) ::timeout)]
53+
(if (identical? ::timeout result)
54+
(throw (java.util.concurrent.TimeoutException.))
5455
result)))
55-
(get [this timeout unit]
56-
(let [result (deref this (.toMillis unit timeout) ::timeout)]
57-
(case result
58-
::cancelled (throw (java.util.concurrent.CancellationException.))
59-
::timeout (throw (java.util.concurrent.TimeoutException.))
60-
result)))
61-
(isCancelled [_] @cancelled?)
62-
(isDone [this] (or (.isRealized this) (.isCancelled this)))
63-
(cancel [this _interrupt?]
64-
(if (.isDone this)
56+
(isCancelled [_] (p/cancelled? p))
57+
(isDone [_] (p/done? p))
58+
(cancel [_ _interrupt?]
59+
(if (p/done? p)
6560
false
66-
(if (compare-and-set! cancelled? false true)
67-
(do
68-
(protocols.endpoint/send-notification server "$/cancelRequest" {:id id})
69-
(deliver p ::cancelled)
70-
true)
71-
false))))
61+
(do
62+
(p/cancel! p)
63+
(protocols.endpoint/send-notification server "$/cancelRequest" {:id id})
64+
true)))
65+
p.protocols/IPromiseFactory
66+
(-promise [_] p))
7267

7368
;; Avoid error: java.lang.IllegalArgumentException: Multiple methods in multimethod 'simple-dispatch' match dispatch value: class lsp4clj.server.PendingRequest -> interface clojure.lang.IPersistentMap and interface clojure.lang.IDeref, and neither is preferred
7469
;; Only when CIDER is running? See https://github.com/thi-ng/color/issues/10
@@ -96,8 +91,7 @@
9691
Sends `$/cancelRequest` only once, though `lsp4clj.server/deref-or-cancel` or
9792
`future-cancel` can be called multiple times."
9893
[id method started server]
99-
(map->PendingRequest {:p (promise)
100-
:cancelled? (atom false)
94+
(map->PendingRequest {:p (p/deferred)
10195
:id id
10296
:method method
10397
:started started
@@ -222,7 +216,7 @@
222216
(async/put! trace-ch [:debug trace-body])))
223217

224218
(defrecord PendingReceivedRequest [result-promise cancelled?]
225-
promesa.protocols/ICancellable
219+
p.protocols/ICancellable
226220
(-cancel! [_]
227221
(p/cancel! result-promise)
228222
(reset! cancelled? true))
@@ -330,7 +324,7 @@
330324
(if-let [{:keys [p started] :as req} (get pending-requests id)]
331325
(do
332326
(trace this trace/received-response req resp started now)
333-
(deliver p (if error resp result)))
327+
(p/resolve! p (if error resp result)))
334328
(trace this trace/received-unmatched-response resp now)))
335329
(catch Throwable e
336330
(log-error-receiving this e resp))))

test/lsp4clj/server_test.clj

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
(is (nil? (server/send-notification server "req" {:body "foo"})))
134134
(server/shutdown server)))
135135

136-
(deftest should-receive-receive-response-to-request-sent-while-processing-notification
136+
(deftest should-receive-response-to-request-sent-while-processing-notification
137137
;; https://github.com/clojure-lsp/clojure-lsp/issues/1500
138138
(let [input-ch (async/chan 3)
139139
output-ch (async/chan 3)
@@ -316,6 +316,46 @@
316316
(.get req 100 java.util.concurrent.TimeUnit/MILLISECONDS)))
317317
(server/shutdown server))))
318318

319+
(deftest request-should-behave-like-a-promesa-promise
320+
(testing "before being handled"
321+
(let [input-ch (async/chan 3)
322+
output-ch (async/chan 3)
323+
server (server/chan-server {:output-ch output-ch
324+
:input-ch input-ch})
325+
_ (server/start server nil)
326+
req (p/promise (server/send-request server "req" {:body "foo"}))]
327+
(is (not (p/done? req)))
328+
(server/shutdown server)))
329+
(testing "after response"
330+
(let [input-ch (async/chan 3)
331+
output-ch (async/chan 3)
332+
server (server/chan-server {:output-ch output-ch
333+
:input-ch input-ch})
334+
_ (server/start server nil)
335+
req (p/promise (server/send-request server "req" {:body "foo"}))
336+
client-rcvd-msg (h/assert-take output-ch)]
337+
(async/put! input-ch (lsp.responses/response (:id client-rcvd-msg) {:processed 1}))
338+
(is (= {:processed 2} (-> req
339+
(p/timeout 1000 :test-timeout)
340+
(p/then #(update % :processed inc))
341+
(p/extract))))
342+
(is (p/done? req))
343+
(is (p/resolved? req))
344+
(is (not (p/rejected? req)))
345+
(is (not (p/cancelled? req)))
346+
(server/shutdown server)))
347+
(testing "after cancellation"
348+
(let [input-ch (async/chan 3)
349+
output-ch (async/chan 3)
350+
server (server/chan-server {:output-ch output-ch
351+
:input-ch input-ch})
352+
_ (server/start server nil)
353+
req (p/promise (server/send-request server "req" {:body "foo"}))]
354+
(p/cancel! req)
355+
(is (p/done? req))
356+
(is (p/cancelled? req))
357+
(server/shutdown server))))
358+
319359
(def fixed-clock
320360
(-> (java.time.LocalDateTime/of 2022 03 05 13 35 23 0)
321361
(.toInstant java.time.ZoneOffset/UTC)

0 commit comments

Comments
 (0)