🎉 Update: The recommended patch from CLJ-1472 resolves this issue. This patch is included in the Clojure 1.10.2 test release. We strongly encourage you to try it out with your projects and report back any issues to the Clojure core team.
Clojure 1.10 introduced locking code into clojure.spec.alpha
that often causes GraalVM's native-image
to fail with:
Error: unbalanced monitors: mismatch at monitorexit, 96|LoadField#lockee__5436__auto__ != 3|LoadField#lockee__5436__auto__
Call path from entry point to clojure.spec.gen.alpha$dynaload$fn__2628.invoke():
at clojure.spec.gen.alpha$dynaload$fn__2628.invoke(alpha.clj:21)
See CLJ-1472 for a detailed explanation of the cause of this failure and the Clojure core team's approach to addressing it.
Although no longer necessary nor recommended, you can create a local patched version of Clojure by following the instructions below under Scripts.
🎉 Update: Thanks for voting! The Clojure core team has has addressed this issue in Clojure 1.10.2.
Using a patched version of Clojure is not ideal. If you are interested in getting this issue fixed in a next release of Clojure, consider upvoting it on ask.clojure.org.
Builds and locally installs Clojure with a patch from CLJ-1472.
Audience
We have identified 2 primary users of this script:
- Clojure tool developer - wants to natively compile their app. This person will use a 1.10.1 patched version of Clojure and will get by without specifying any options to the script.
- Clojure core developer - works on Clojure itself and wants to make progress on CLJ-1472. This person will likely work off HEAD of Clojure master but may want also to select different commits and/or patches.
Usage
Usage: build-clojure-with-1472-patch.sh [options...]
-h, --help
-p, --patch-filename <filename>
name of patch file to download from CLJ-1472
defaults to the currently recommended clj-1472-5.patch
-c, --clojure-commit <commit>
choose clojure commit to patch, can be sha or tag
specify HEAD for most recent commit
defaults to "clojure-10.0.1" tag
-w, --work-dir <dir name>
temporary work directory
defaults to system generated temp dir
NOTE: for safety, this script will only delete what it creates under specified work dir
CLJ-1472 considered patches are:
clj-1472-5.patch
- At the time of this writing, there are no other candidates under consideration.
Note that the script will download the patch for you.
The built version contains the clojure git short sha and a modified form of the patch filename in its version.
Prerequisites
The script will fail if any of the following are not found:
- clojure
- git
- git-extras
- maven - tested with v3.6.3, check that your maven version is recent
- jet - see jet installation instructions (Interesting tidbit: jet is a Clojure program compiled to a native image with GraalVM)
- curl
- sed
If on macOS, any missing prerequisites can be installed via brew.
Common Usage Example
Most folks will run without options:
./build-clojure-with-1472-patch.sh
defaults to the recommended clj-1472-5.patch
and clojure clojure-1.10.1 (which has a short sha of 38bafca9
) and installs the following to your local maven repo:
org.clojure/clojure 1.10.1-patch_38bafca9_clj_1472_5
org.clojure/spec.alpha 0.2.176-patch_38bafca9_clj_1472_5
Alternate Usage: Specifying a Patch
./build-clojure-with-1472-patch.sh -p CLJ-1472-reentrant-finally2.patch
defaults to clojure tag clojure-1.10.1 and locally installs:
org.clojure/clojure 1.10.1-patch_38bafca9_clj_1472_reentrant_finally2
org.clojure/spec.alpha 0.2.176-patch_38bafca9_clj_1472_reentrant_finally2
Alternate Usage: Specifying a Commit
./build-clojure-with-1472-patch.sh -c HEAD
defaults to clj-1472-5.patch
, selects clojure HEAD commit (which has a short sha of 653b8465
at the time of this writing) and installs:
org.clojure/clojure 1.11.0-master_patch_653b8465_clj_1472_5-SNAPSHOT
org.clojure/spec.alpha 0.2.176-patch_653b8465_clj_1472_5
Alternate Usage: Specifying a Patch and a Commit
./build-clojure-with-1472-patch.sh \
-p CLJ-1472-reentrant-finally2.patch \
-c c9a45b5f8afc2c4dfcce7f2e23dadc8749b9fd0d
installs:
org.clojure/clojure 1.10.0-beta8-patch_c9a45b5f_clj_1472_reentrant_finally2
org.clojure/spec.alpha 0.2.176-patch_c9a45b5f_clj_1472_reentrant_finally2
Referencing a Patched Clojure
The patched version of Clojure should work with GraalVM's native-image
, reference the variant you want. Example dependencies for deps.edn
:
{org.clojure/clojure {:mvn/version "1.10.1-patch_38bafca9_clj_1472_5"}}
{org.clojure/clojure {:mvn/version "1.10.1-patch_38bafca9_clj_1472_reentrant_finally2"}}
-
Update
deps.edn
to reflect the version of Clojure you want to test. Verify the Clojure version by runningclojure -Stree
. -
If the
native-image
binary is not on thePATH
, set either:- the
GRAALVM_HOME
environment variable to the location of your GraalVM installation - the
NATIVE_IMAGE
environment variable to the location of GraalVM'snative-image
command.
- the
-
Run
./compile
, after some output that looks similar to this:spec-test.core [spec-test:46447] classlist: 3,710.61 ms, 1.15 GB [spec-test:46447] (cap): 2,793.84 ms, 1.15 GB [spec-test:46447] setup: 4,119.13 ms, 1.15 GB [spec-test:46447] (typeflow): 13,646.25 ms, 3.01 GB [spec-test:46447] (objects): 6,452.33 ms, 3.01 GB [spec-test:46447] (features): 497.04 ms, 3.01 GB [spec-test:46447] analysis: 21,187.40 ms, 3.01 GB [spec-test:46447] (clinit): 360.62 ms, 3.26 GB [spec-test:46447] universe: 1,068.79 ms, 3.26 GB [spec-test:46447] (parse): 1,660.49 ms, 3.26 GB [spec-test:46447] (inline): 1,942.10 ms, 3.31 GB [spec-test:46447] (compile): 12,632.50 ms, 4.28 GB [spec-test:46447] compile: 17,143.04 ms, 4.28 GB [spec-test:46447] image: 2,310.31 ms, 4.28 GB [spec-test:46447] write: 743.65 ms, 4.28 GB [spec-test:46447] [total]: 50,826.31 ms, 4.28 GB
you should now have a
spec-test
executable. -
Run
./spec-test
. This should produce output like the following:{:major 1, :minor 10, :incremental 2, :qualifier alpha1} true
-
Optionally rerun
./compile
against Clojure1.10.1
unpatched. You should getunbalanced monitors
errors.
Here we look at the performance impact of CLJ-1472 patches on Clojure in absence of GraalVM.
To run an individual performance test against Clojure patched with current recommended CLJ-1472 patch. Patched Clojure must already be installed, see Scripts above:
clojure -J-XX:-EliminateLocks -A:performance
(We use -J-XX:-EliminateLocks
to prevent the JVM from eliding locks.)
This should output something like the following:
Java version: 11.0.6
Clojure version: 1.10.1
"Elapsed time: 23217.11897 msecs"
Success
The babashka perftest.clj
script runs the performance test against Clojure 1.10.1
unpatched and Clojure 1.10.1
with CLJ-1472 current and previous candidate patches.
Patches are assumed to be already installed, see Scripts above.
Examples results from a Late 2013 iMac with Quad-Core Intel i7 running macOS 10.15.3.
perftest.clj
was run twice against the Amazon Corretto JVM; once against v1.8 then once against v11.0.6.
Times are in milliseconds.
Java Version | JVM Opt | 1.10.1 | 1.10.1‑patch 38bafca9 clj 1472 4 | 1.10.1‑patch 38bafca9 clj 1472 5 | 1.10.1‑patch 38bafca9 clj 1472 reentrant finally2 |
---|---|---|---|---|---|
1.8.0_242 | <none> | 23868.683118 | 23235.997299 | 23686.511219 | 23720.294582 |
1.8.0_242 | ‑J‑XX:‑EliminateLocks | 23495.019667 | 23461.609831 | 22877.822631 | 23050.786466 |
11.0.6 | <none> | 22563.156409 | 22601.63469 | 23592.760502 | 22018.846114 |
11.0.6 | ‑J‑XX:‑EliminateLocks | 22569.031807 | 23240.028719 | 23122.333266 | 24844.203457 |
If you cannot, for whatever reason, use Clojure 1.10.2, here are some other workarounds:
-
create your own patched version of Clojure by following instructions under Scripts above.
-
clojurl introduces a Java-level special form and patches selections of Clojure code at run-time: link
-
rep builds GraalVM binaries using an automated patched Clojure build: link
It keeps pre-built jars of clojure and spec here
-
babashka vendors code from Clojure and works around the locking issues manually. This is a patched version of
clojure.core.server
. -
revert to using Clojure 1.9.0