babashka

ahungry 2025-05-08T19:42:58.183049Z

hi friends, I was trying out httpkit server, and run-server was not halting the app to listen for connections - I found another sample in the wild where someone ends their .bb file with:

@(promise)
why is this necessary? it's a promise that will never resolve/deref right? in plain old clojure though, I don't think such a thing is necessary for httpkit (is it?)

2025-05-08T20:04:22.944039Z

bb seems more aggressive than clojure when shutting down. compare bb -e '(future @(promise))' and clojure -e '(future @(promise))'.

👍 1
🙏 1
borkdude 2025-05-08T20:10:05.914929Z

clojure will wait a bit before shutting down, you need to call shutdown-agents manually but bb does this for you clojure -X or -T (forgot which) has altered this behavior for clojure as well since it just makes more sense for scripts/tools

🙏 1
borkdude 2025-05-08T20:12:55.288239Z

but bb simply just calls shutdown-agents at the end of the script

borkdude 2025-05-08T20:14:22.300679Z

Perhaps I could try to do the daemon thread approach in bb, but so far this worked out ok

👌 1
borkdude 2025-05-09T10:01:48.135239Z

I double checked an httpkit server example invoked with -X and it doesn't seem to quit right away:

(ns script)

(require '[org.httpkit.server :refer [run-server]])

(defn handler [_req]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body "Hello from http-kit in Babashka!"})

(defn doit [_]
  (run-server handler {:port 8080})
  (println "Server running at "))

;; @(promise) ; Keeps the server running
$ clj -X script/doit
Server running at 
Although httpkit does seem to spawn daemon threads. Not sure why it doesn't quit right away with -X here

borkdude 2025-05-09T10:08:57.987979Z

Ah, it's probably because there is one ServerThread which is not a daemon thread: https://github.com/http-kit/http-kit/blob/eefe7c13c0ed36283d26bf8b2d936e1ae69465a5/src/java/org/httpkit/server/HttpServer.java#L450

borkdude 2025-05-09T10:12:44.664409Z

Yeah, this is the difference:

(ns script)

(defn doit [_]
  (-> (doto (new java.lang.Thread (fn []
                                    (Thread/sleep 1000000)) )
        (.setDaemon true)
        (.start)))
  (println "Server running at "))
So if you remove .setDaemon the script will immediately return with clojure -X

borkdude 2025-05-09T18:06:06.093399Z

Oh no, wait, it's not about shutdown-agents, it's about bb doing a System/exit :)

(defn doit [_]
  (-> (doto (new java.lang.Thread (fn []
                                    (Thread/sleep 1000000)) )
        #_(.setDaemon true)
        (.start)))
  (println "Server running at ")
  (System/exit 0))
This is basically what's happening

borkdude 2025-05-09T18:10:22.138789Z

I guess if I would change babashka.main to:

(let [exit-code (run args)]
      (when-not (zero? exit-code)
        (System/exit exit-code)))
+ installing the daemon executor, it would resemble clojure closer

borkdude 2025-05-09T18:13:13.029859Z

Let's see if CI likes this commit or not... https://github.com/babashka/babashka/commit/f4f1d9e94509a08f2b0ae35d9cda0060ba76e7f7

borkdude 2025-05-09T18:30:04.441549Z

lol:

lein test babashka.agent-test
=== agent-binding-conveyance-test


Too long with no output (exceeded 10m0s): context deadline exceeded

😱 1
borkdude 2025-05-09T18:32:00.737259Z

locally this test does just terminate though:

$ ./bb -e '(def ^:dynamic *foo* 1) (def a (agent nil)) (binding [*foo* 2] (send-off a (fn [_] *foo*))) (await a) @a'
2

borkdude 2025-05-09T18:40:11.682569Z

With the change in place, this does still work but hangs:

(import '(java.util.concurrent Executors ExecutorService))
(let [fut (.submit ^ExecutorService (Executors/newCachedThreadPool) ^Callable (fn [] 3))]
  (prn (.get fut)))
When I call shutdown on the thread pool executor then the script shuts down I guess this would be a breaking change to bb really.

borkdude 2025-05-09T18:44:26.365889Z

I would revisit this if people were adamant about changing this behavior.

ahungry 2025-05-09T19:13:58.595719Z

The workaround doesn't seem too tricky, although I wouldn't have figured it out for awhile if I didn't see it in another sample code - maybe just a note would suffice, or somehow tie to httpkit's start call in stderr