babashka

borkdude 2025-06-13T17:51:40.844589Z

Promesa just merged my PR and it's now babashka compatible! https://github.com/funcool/promesa Thanks @niwinz!

16
❤️ 15
🎉 11
h0bbit 2025-06-20T05:28:06.477749Z

Thank you so much @borkdude! I will test this in the next week and update my repos! 😄

borkdude 2025-06-13T17:52:25.652869Z

@h0bbit you mentioned this on reClojure, so here you go!

2025-06-13T18:11:48.826619Z

That should allow promesa to inherit future fixes to binding-conveyor-fn. IIRC it has a memory leak.

2025-06-13T18:13:47.832809Z

Different one, but I forgot the fix didn't belong in binding-conveyor-fn. https://clojure.atlassian.net/browse/CLJ-2619

2025-06-13T18:14:14.970929Z

The caller needs to clear the bindings manually. Maybe promesa also has this leak.

niwinz 2025-06-13T18:19:53.607689Z

the memory leak happens because the thread used is pooled so it still active and its thread local still points to the captured frame

technosophist 2025-06-13T18:21:02.733919Z

interesting

niwinz 2025-06-13T18:21:03.522809Z

i guess when the thread is stoped for inactivity or the frame is overwritten with other value, then, the previous object will be available for GC

niwinz 2025-06-13T18:22:00.226919Z

This problem (memory leak) should not happen when used with VirtualThreads because they are not pooled by its nature

borkdude 2025-06-13T18:22:34.857619Z

VT all the things!

2025-06-13T18:31:48.604189Z

True, an efficient fix would only clear the binding frame when the thread is pooled.

niwinz 2025-06-13T18:34:06.537209Z

On the other hand, I don't think the clearing will penalize a lot. I mean the performance overhead is on push/pop operations, if we simply reset it to an empty frame i guess it will not add too much overhead. I will measure it

👏 2
2025-06-13T18:36:36.266819Z

In CLJ-2619-future-memory-leak-4.patch I wasn't confident enough that it was ok to clear the frame, instead I did this:

(let [o (clojure.lang.Var/getThreadBindingFrame)]
  (try (f)
       (finally (clojure.lang.Var/resetThreadBindingFrame o)))

niwinz 2025-06-13T18:38:15.400859Z

interesting

2025-06-13T18:40:23.619849Z

I wonder if that reintroduces the previous memory leak.

niwinz 2025-06-13T18:41:34.832149Z

do you have info/link for the previous memory leak?

2025-06-13T18:42:09.616999Z

Looks like it was related to agents rather than futures.

2025-06-13T18:42:20.530759Z

but I didn't understand how it worked.

niwinz 2025-06-13T18:42:36.110729Z

yeah, but i think this change will not introduce back the prev memory leak issue

niwinz 2025-06-13T18:43:02.949909Z

because we just restore on the target thread thre frame previous to reset

niwinz 2025-06-13T18:44:10.452009Z

the prev memory leak is related to use the same frame from parent thread on the target thread, and this is replaced by clone operation for avoid it

borkdude 2025-06-13T18:44:43.571159Z

so just a clojure.lang.Var/resetThreadBindingFrame empty-frame should do it?

niwinz 2025-06-13T18:44:59.835639Z

I think yes

niwinz 2025-06-13T18:46:57.517169Z

but for make it available on promesa we sill need to reintroduce a local copy of conveyor helper hehe

2025-06-13T18:48:41.567109Z

I think it could be in binding-conveyor-inner fwiw

borkdude 2025-06-13T18:48:55.157829Z

no problem, leave binding-conveyor-fn* as it is for bb and make a custom one for clj

borkdude 2025-06-13T18:49:20.399119Z

in the next version of bb clojure.lang.Var/{clone,reset,get}ThreadBindingFrame will work, I'm working on it

2025-06-13T18:49:37.238409Z

oh, I missed that.

2025-06-13T18:51:38.773949Z

I guess with-bindings /`get-bindings` might also work for bb?

borkdude 2025-06-13T18:52:07.759299Z

@niwinz here's how you do it using reader conditionals:

(def binding-conveyor-fn* #?(:bb @#'clojure.core/binding-conveyor-fn :clj your-custom-version)
since :bb is read first by bb. @ambrosebs then you migth as well use bound-fn*

👍🏽 1
2025-06-13T18:52:27.154559Z

yeah sorry I'm muddled up.

niwinz 2025-06-13T18:53:23.222729Z

I will read this thread later, tomorrow probably (traveling) o/

borkdude 2025-06-13T18:53:59.381709Z

have fun!

👏 1
❤️ 1
2025-06-13T18:54:55.043759Z

my idea was basically (#?(:bb bound-fn* :default binding-conveyor-fn*) ifn) if you need to experiment before bb supports custom conveyor.

borkdude 2025-06-13T19:00:10.604959Z

I'd prefer bb then to use just the normal clojure.core/binding-conveyor-fn

👍 1
borkdude 2025-06-13T19:01:36.310559Z

I asked claude to write me a test for testing the interop of clojure.lang.Var/.. in bb but it came up with utter nonsense. I wrote this one myself:

(deftest clojure-lang-Var-binding-frame-test
  (is (= [43 42 43 42] (bb nil "(def ^:dynamic *test-var* 42)
   (def results (atom []))
   (binding [*test-var* *test-var*]
    (let [frame (clojure.lang.Var/cloneThreadBindingFrame)]
      (binding [*test-var* 43]
        (let [inner-frame (clojure.lang.Var/getThreadBindingFrame)]
          (swap! results conj *test-var*)
          (clojure.lang.Var/resetThreadBindingFrame frame)
          (swap! results conj *test-var*)
          (clojure.lang.Var/resetThreadBindingFrame inner-frame)
          (swap! results conj *test-var*)))
      (swap! results conj *test-var*)))
   @results"))))
Not related to threads, but it does test what it's supposed to be doing

2025-06-13T19:26:14.744899Z

curious how you test cloneThreadBindingFrame. I still don't understand it.

2025-06-13T19:26:48.159319Z

oh that does test it. Rather, test the difference between get/clone.

2025-06-13T19:28:34.307959Z

doh. reading closer. what's the subtleties here? please help me understand frame vs inner-frame?

borkdude 2025-06-13T19:29:14.289329Z

ah I could add an identical? check for clone

borkdude 2025-06-13T19:29:20.460729Z

inner-frame has the additional binding

2025-06-13T19:29:42.448889Z

why did you use get vs clone for inner-frame?

borkdude 2025-06-13T19:30:58.640589Z

(deftest clojure-lang-Var-binding-frame-test
  (is (= [43 42 43 42] (bb nil "(def ^:dynamic *test-var* 42)
   (def results (atom []))
   (binding [*test-var* *test-var*]
    (let [current-frame (clojure.lang.Var/cloneThreadBindingFrame)
          frame (clojure.lang.Var/cloneThreadBindingFrame)]
      (assert (not (identical? current-frame frame)))
      (binding [*test-var* 43]
        (let [inner-frame (clojure.lang.Var/getThreadBindingFrame)]
          (swap! results conj *test-var*)
          (clojure.lang.Var/resetThreadBindingFrame frame)
          (swap! results conj *test-var*)
          (clojure.lang.Var/resetThreadBindingFrame inner-frame)
          (swap! results conj *test-var*)))
      (swap! results conj *test-var*)))
   @results"))))
Added identical? check

borkdude 2025-06-13T19:31:05.063239Z

I used get so I also exercise that method

👍 1
2025-06-13T19:35:34.530549Z

I'm thinking along the lines of unit testing https://github.com/clojure/clojure/commit/12f07da889819bc5613546ec223e97ac27c86dbf

2025-06-13T19:36:27.865479Z

a unit test that would only pass if it used clone instead of get.

borkdude 2025-06-13T19:36:45.864929Z

would be sweet if you could come up with one.

2025-06-13T19:36:56.256259Z

agreed!