This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-08-25
Channels
- # announcements (4)
- # asami (26)
- # babashka (82)
- # beginners (27)
- # biff (6)
- # boot (1)
- # calva (42)
- # cider (2)
- # clj-commons (1)
- # clj-http-lite (2)
- # clj-kondo (37)
- # cljdoc (1)
- # clojure (46)
- # clojure-europe (34)
- # clojure-nl (1)
- # clojure-norway (7)
- # clojure-uk (2)
- # clojurescript (54)
- # code-reviews (18)
- # cursive (2)
- # datalevin (32)
- # datomic (7)
- # etaoin (1)
- # fulcro (9)
- # gratitude (3)
- # hyperfiddle (15)
- # introduce-yourself (1)
- # jobs (2)
- # lsp (32)
- # nrepl (1)
- # off-topic (18)
- # pathom (17)
- # pedestal (5)
- # polylith (89)
- # reitit (7)
- # releases (3)
- # remote-jobs (4)
- # shadow-cljs (52)
- # spacemacs (3)
- # squint (14)
- # tools-build (10)
- # tools-deps (18)
- # vim (4)
- # xtdb (34)
Would it be possible to implement a finalize
method on a java object in bb?
clojure:
user=> (reify java.lang.Object (finalize [this] (println "collected")))
#object[user$eval138$reify__139 0x29182679 "user$eval138$reify__139@29182679"]
bb:
user=> (reify java.lang.Object (finalize [this] (println "collected")))
java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.lang.Throwable [at <repl>:1:1]
I have a pod. And on the pod side I have objects on the heap. And on the bb side I can new
those objects. And the bb gets a keyword to refer to the instance, and uses that keyword to refer to it in future calls. This is all cool, except the instance may get garbage collected over on the pod. So, I can keep a reference to it so it doesn't. But then I need a delete
call on the bb side to clean that up. And the user needs to manage those instances or they will build up and use more and more ram. It would be great if the bb side could automatically send those delete calls when the referering keyword (or other object) on the bb side gets garbage collected. Essentially moving the life-cycle management from the pod to the bb garbage collector.
There are two ways I can think of achieving this. One is to have the referring object on the bb side call something in its finalize
. This could be a finalize on a clojure.lang.Keyword, but that may be difficult/impossible even in clojure (https://clojure.atlassian.net/browse/CLJ-911). Or the object with finalize may not be a keyword, but a keyword-like re-implementation. A reified instance implementing all the protocols that a Keyword does (IFn, Comparable, Named, Serializable, IHashEq).
The other way might be to have a java.util.WeakHashMap, java.lang.ref.WeakReference and a java.lang.ref.ReferenceQueue to register the keywords into and trigger the appropriate delete
calls on GC.
(bb has java.util.WeakHashMap exposed, but not java.lang.ref.WeakReference or java.lang.ref.ReferenceQueue)
Ok this morning I have been doing some experiments with weakreferences. Added java.lang.ref.WeakReference to bb. Some interesting discoveries. Triggering GC in clojure doesn't destruct keywords. But it does destruct java Objects. And in bb also keywords don't destruct. But sometimes objects do, and sometimes they do not. I wonder if something holds a reference to keywords inside clojure. And similarly in bb with Objects (but only sometimes?)
(ns test
(:import [java.lang.ref WeakReference]))
(def weakref (atom nil))
(defn gc []
(println "gc")
(System/gc)
(System/runFinalization)
(println "gc done")
)
(defn force-gc []
(println "force-gc")
(let [t (atom (Object.))
wr (WeakReference. @t)]
(reset! t nil)
(while (.get wr)
(System/gc)
(System/runFinalization)))
(println "force-gc done"))
(defn main []
(let [a (Object.)]
(println "inside")
(reset! weakref (WeakReference. a))
(println "a:" a)
(println "wr:" (.get @weakref))
(force-gc)
(println "a:" a)
(println "wr:" (.get @weakref)))
(println "outside")
(println "wr:" (.get @weakref))
(force-gc)
(println "wr:" (.get @weakref))
)
(main)
I have a standard gc
call (that may or may not actually trigger a gc) and a force-gc
which blocks until it does
$ clj test/bb-gc.clj
inside
a: #object[java.lang.Object 0x5167268 java.lang.Object@5167268]
wr: #object[java.lang.Object 0x5167268 java.lang.Object@5167268]
force-gc
force-gc done
a: #object[java.lang.Object 0x5167268 java.lang.Object@5167268]
wr: #object[java.lang.Object 0x5167268 java.lang.Object@5167268]
outside
wr: #object[java.lang.Object 0x5167268 java.lang.Object@5167268]
force-gc
force-gc done
wr: nil
$ bb test/bb-gc.clj
inside
a: #object[java.lang.Object 0x2d3102ab java.lang.Object@2d3102ab]
wr: #object[java.lang.Object 0x2d3102ab java.lang.Object@2d3102ab]
force-gc
force-gc done
a: #object[java.lang.Object 0x2d3102ab java.lang.Object@2d3102ab]
wr: #object[java.lang.Object 0x2d3102ab java.lang.Object@2d3102ab]
outside
wr: #object[java.lang.Object 0x2d3102ab java.lang.Object@2d3102ab]
force-gc
force-gc done
wr: #object[java.lang.Object 0x2d3102ab java.lang.Object@2d3102ab]
but it's interesting... because the WeakReference
used to check if GC had actually run, used inside force-gc
does work in bb
so one Object gets destroyed by GC, but the other does not, as if something hidden is holding a reference to it
and the GC does clean up the object in bb. So it seems that internally it doesnt hold a ref in the use of the atom (inside force-gc
), it doesn't hold a ref on exit from a function (`main`) but it does hold a ref on exit from a let
body
I think a java.lang.ref.WeakReference
might be enough to move pod object lifecycle control into bb mainline...
ok.. some more info for the interested. Keywords do GC, but not if they are every used as a literal (obvious in retrospect). So (keyword "foo")
behaves just like (Object.)
. But :foo
never GCs
first
here: https://github.com/babashka/babashka/blob/master/reify/src/babashka/impl/reify2.clj#L56
interfaces is a set, too. So if you do a multi interface reify, which interface actually has its methods implemented is not obvious
yes, only a single interface is supported. supporting multiple would lead to a combinatorial explosion
user=> (def a (reify java.lang.Object (toString [_] "do") clojure.lang.Seqable (seq [_] '(1 2 3))))
#'user/a
user=> (seq a)
(1 2 3)
user=> (str a)
"babashka.impl.clojure.lang.Seqable@615b1baf"
user=> (reify java.lang.Object)
java.lang.ClassCastException: class clojure.lang.Symbol cannot be cast to class java.lang.Throwable (clojure.lang.Symbol is in unnamed module of loader 'app'; java.lang.Throwable is in module java.base of loader 'bootstrap') [at <repl>:7:1]
like there are temporary instances being created, and they get finalized in the first gc
closed that finalize PR. But made this one with what I discovered along the way https://github.com/babashka/babashka/pull/1348
@U1QQJJK89 please don't use finalize, the thing you are looking for is a Cleaner
(ns test
(:import [java.lang.ref Cleaner]))
(def global-cleaner (Cleaner/create))
(defn cleanup-code [internals]
(println "cleaning up object" internals))
(deftype ThingCleanup
[thing]
Runnable
(run [_]
(cleanup-code thing)))
(defn do-thing
[]
(let [object {:name "bob"
:whatever 123}]
(.register global-cleaner
object
;; Can't hold a ref to the object itself
(->ThingCleanup (into {} object)))
123))
(defn main
[]
(do-thing)
(loop []
(Thread/sleep 100)))
(future (main))
but that is the proper mechanism usually - cleaner runs a thread that consumes from a queue of weakrefs
Thanks @U3JH98J4R I will experiment. It could be made to work...