GraalVM native-image build-time Feature: initialize Clojure namespaces the right way so build-time class initialization doesn't blow up.
The problem: graal-build-time registers every Clojure-generated package for
build-time class initialization. native-image then runs each <clinit> RAW on
a parallel analysis worker thread, with no Clojure thread-binding frame. Any
namespace whose body has a top-level (set! *warn-on-reflection* true) (most
libraries — babashka.fs, next.jdbc, rewrite-clj, honeysql, nippy, …) throws
java.lang.IllegalStateException: Can't change/establish root binding of:
*warn-on-reflection* with set
because set! on a dynamic var requires a thread binding. Core namespaces
(clojure.string, clojure.spec.alpha, …) survive only because clojure.core's
bootstrap loads them through require, which DOES push that binding. Libraries
reached directly by the analysis get no such courtesy.
The fix: in beforeAnalysis — which runs in the image-builder JVM, before the
analysis can raw-init anything — require every app + extension namespace with
the compiler vars bound. require initializes each class through Clojure's
loader (binding active), so its set! succeeds; by the time the analysis marks
the class build-time-initialized it is already initialized and is not re-run.
Wired via --features=com.blockether.vis.internal.nativeimage in main's
resources/META-INF/native-image/com.blockether/vis/native-image.properties,
alongside graal-build-time's feature. Build-time only — never loaded at runtime.
GraalVM native-image build-time Feature: initialize Clojure namespaces the
*right* way so build-time class initialization doesn't blow up.
The problem: `graal-build-time` registers every Clojure-generated package for
build-time class initialization. native-image then runs each `<clinit>` RAW on
a parallel analysis worker thread, with no Clojure thread-binding frame. Any
namespace whose body has a top-level `(set! *warn-on-reflection* true)` (most
libraries — babashka.fs, next.jdbc, rewrite-clj, honeysql, nippy, …) throws
java.lang.IllegalStateException: Can't change/establish root binding of:
*warn-on-reflection* with set
because `set!` on a dynamic var requires a thread binding. Core namespaces
(clojure.string, clojure.spec.alpha, …) survive only because clojure.core's
bootstrap loads them through `require`, which DOES push that binding. Libraries
reached directly by the analysis get no such courtesy.
The fix: in `beforeAnalysis` — which runs in the image-builder JVM, before the
analysis can raw-init anything — `require` every app + extension namespace with
the compiler vars bound. `require` initializes each class through Clojure's
loader (binding active), so its `set!` succeeds; by the time the analysis marks
the class build-time-initialized it is already initialized and is not re-run.
Wired via `--features=com.blockether.vis.internal.nativeimage` in main's
`resources/META-INF/native-image/com.blockether/vis/native-image.properties`,
alongside graal-build-time's feature. Build-time only — never loaded at runtime.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |