diff --git a/src/tbd/nrepl/core.clj b/src/tbd/nrepl/core.clj index d3fbf71..8a429f9 100644 --- a/src/tbd/nrepl/core.clj +++ b/src/tbd/nrepl/core.clj @@ -1,7 +1,8 @@ (ns tbd.nrepl.core "The on-demand nREPL debug server. No HTTP here." (:require [clojure.string :as str] - [nrepl.server :as nrepl])) + [nrepl.server :as nrepl]) + (:import (java.net InetAddress InetSocketAddress Socket))) (def default-port 7888) @@ -35,11 +36,32 @@ (reset! state {:server server :bind bind :port (:port server)}))) (status))) +(defn- accepting? + "True if something takes connections on `bind`:`port` (a wildcard bind is probed via loopback)." + [bind port] + (let [addr (InetAddress/getByName bind) + addr (if (.isAnyLocalAddress addr) (InetAddress/getLoopbackAddress) addr)] + (try (with-open [s (Socket.)] + (.connect s (InetSocketAddress. addr (int port)) 200) + true) + (catch java.io.IOException _ false)))) + +(defn- await-closed + "Blocks until `bind`:`port` refuses connections, for up to two seconds. Closing a server socket + doesn't stop the kernel queueing connections while the accept loop is still blocked in accept(), + so for a moment after `stop-server` the port can still be connected to." + [bind port] + (let [deadline (+ (System/nanoTime) 2000000000)] + (while (and (accepting? bind port) (< (System/nanoTime) deadline)) + (Thread/sleep 5)))) + (defn disable! - "Stops the server if it's running; returns the status." + "Stops the server if it's running, and returns once its port really refuses connections; + returns the status." [] (locking state - (when-let [{:keys [server]} @state] + (when-let [{:keys [server bind port]} @state] (nrepl/stop-server server) + (await-closed bind port) (reset! state nil)) (status)))