Skip to content

Commit f688c4a

Browse files
committed
Survive remote connection failures in the bridge accept loop
Test-driving the bridge with a wrong --token showed a connecting client hanging forever: the failed WebSocket handshake threw inside the accept loop, killing it silently and leaving the local socket open, with all subsequent connections ignored. Now a failed relay setup closes the client socket immediately (the client errors out instead of hanging), logs the cause to stderr, and the bridge keeps accepting. The remote side is opened before any local transport resources so a failure has nothing to leak.
1 parent 1fbc743 commit f688c4a

2 files changed

Lines changed: 35 additions & 4 deletions

File tree

src/drawbridge/bridge.clj

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@
7070
endpoint at `url` until either side disconnects. Calls `on-close`
7171
once when the connection winds down."
7272
[^Socket sock url http-headers poll-opts on-close]
73-
(let [local (transport/bencode sock)
74-
remote (remote-transport url http-headers)
73+
;; Open the remote side first: it's the fallible one (unreachable
74+
;; endpoint, auth rejection), and at this point no local transport
75+
;; resources exist yet for a throw to leak.
76+
(let [remote (remote-transport url http-headers)
77+
local (transport/bencode sock)
7578
{:keys [active-poll-ms idle-poll-ms idle-after-ms]} poll-opts
7679
open? (atom true)
7780
last-activity (atom (System/currentTimeMillis))
@@ -141,8 +144,19 @@
141144
(loop []
142145
(let [^Socket sock (.accept server)]
143146
(swap! connections conj sock)
144-
(relay sock url http-headers poll-opts
145-
#(swap! connections disj sock))
147+
;; A failure to reach the remote endpoint (e.g. a 401 on
148+
;; the first request) must not kill the accept loop, and
149+
;; the local client should see its connection drop right
150+
;; away rather than hang waiting for a handshake.
151+
(try
152+
(relay sock url http-headers poll-opts
153+
#(swap! connections disj sock))
154+
(catch Exception e
155+
(swap! connections disj sock)
156+
(.close sock)
157+
(binding [*out* *err*]
158+
(println "drawbridge.bridge: could not connect to" url "-"
159+
(str e)))))
146160
(recur)))
147161
;; Closing the server socket unblocks accept with an exception.
148162
(catch Exception _)))

test/drawbridge/auth_test.clj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,20 @@
9292
responses (nrepl/message client {:op "eval" :code "(* 6 7)"})]
9393
(is (some #(= "42" (:value %)) responses))))
9494
(finally (bridge/stop-bridge b))))))))
95+
96+
(deftest bridge-rejected-by-endpoint
97+
(testing "clients of a misconfigured bridge fail fast instead of hanging"
98+
(with-secure-server "s3cret"
99+
(fn [port]
100+
(let [b (bridge/start-bridge
101+
{:url (str "http://localhost:" port "/")
102+
:http-headers {"Authorization" "Bearer WRONG"}})]
103+
(try
104+
;; Two rounds: the accept loop must also survive the
105+
;; first failed relay and keep serving new connections.
106+
(dotimes [_ 2]
107+
(is (thrown? Exception
108+
(with-open [conn (nrepl/connect :port (:port b))]
109+
(let [client (nrepl/client conn 20000)]
110+
(doall (nrepl/message client {:op "eval" :code "(+ 1 2)"})))))))
111+
(finally (bridge/stop-bridge b))))))))

0 commit comments

Comments
 (0)