Skip to content

Commit b24643b

Browse files
committed
Handle breaks for Java > 19
1 parent 57a3fba commit b24643b

File tree

12 files changed

+221
-13
lines changed

12 files changed

+221
-13
lines changed

.github/workflows/release.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
push:
1111
branches:
1212
- master
13-
- feature/dockerfile
13+
- fix/break-handling
1414

1515
jobs:
1616
Config:
@@ -67,14 +67,20 @@ jobs:
6767
bb: latest
6868
- name: Update VERSION
6969
run: echo "${{ needs.Config.outputs.version }}" > resources/VERSION
70+
- name: Compile Java
71+
run: bb java:compile
7072
- name: Zip it
7173
run: zip -r deps-try.zip . -x ".git/*"
7274
- name: Build uberjar
7375
run: |
74-
rm -rf .git
76+
# remove stuff not wanted in JAR
77+
rm -rf .git .github
7578
mv deps-try.zip ..
76-
bb uberjar deps-try-bb.jar -m eval.deps-try
79+
80+
bb bb:uberjar
81+
ls -lah deps-try-bb.jar
7782
jar -tf deps-try-bb.jar
83+
7884
mv ../deps-try.zip .
7985
- name: Testrun uberjar
8086
run: bb deps-try-bb.jar -h

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/.clj-kondo
55
.lsp
66
/VERSION
7+
*.class

bb.edn

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{:deps {io.github.eval/deps-try {:local/root "."}}
22
:bbin/bin {deps-try {:main-opts ["-m" "eval.deps-try"]}}
33
:tasks
4-
{gen-manifest {:doc "Generate recipe-manifest and print to stdout"
4+
{java:compile {:doc "Compile files under java/"
5+
:task (shell {:dir "java"} "javac --release 11 dt/JvmtiAgent.java")}
6+
bb:uberjar {:doc "Create the app's uberjar"
7+
:task (shell "bb uberjar deps-try-bb.jar -m eval.deps-try")}
8+
gen-manifest {:doc "Generate recipe-manifest and print to stdout"
59
:requires ([babashka.fs :as fs])
610
:task (exec 'eval.deps-try.recipe/generate&print-manifest
711
{:exec-args

deps.edn

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
{:paths ["src" "resources" "."]
1+
{:paths ["src" "resources" "." "java" "libdt/build"]
22
:deps {borkdude/edamame {:mvn/version "1.4.24"}
3-
cider/orchard {:mvn/version "0.22.0"}
3+
cider/orchard {:mvn/version "0.30.0"}
44
com.bhauman/rebel-readline {:local/root "./vendor/rebel-readline/rebel-readline"}
55
deps-try/cli {:local/root "./vendor/deps-try.cli"}
66
deps-try/http-client {:local/root "./vendor/deps-try.http-client"}
77
io.github.eval/compliment {:git/sha "cd9706db27d456e8940dcd1118174c94effa9358"}
8-
org.clojure/clojure {:mvn/version "1.12.0-alpha11"}
8+
org.clojure/clojure {:mvn/version "1.12.0"}
99
org.clojure/tools.gitlibs {:mvn/version "2.5.197"}
1010
;; suppress logging
1111
org.slf4j/slf4j-nop {:mvn/version "2.0.5"}}

java/dt/JvmtiAgent.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dt;
2+
3+
/**
4+
* Java facade for the C side of the libnrepl JVMTI agent. Currently used to
5+
* stop threads on JDK20+.
6+
*/
7+
public class JvmtiAgent {
8+
9+
// This will bind to Java_nrepl_JvmtiAgent_stopThread function once the
10+
// agent containing this function will be attached.
11+
public static native void stopThread(Thread thread, Throwable throwable);
12+
13+
/**
14+
* Forcibly stop a given thread.
15+
*/
16+
public static void stopThread(Thread thread) {
17+
// ThreadDeath is deprecated, but so it Thread.stop(). We can revisit
18+
// this when JVM actually decides to remove this class.
19+
@SuppressWarnings("deprecation")
20+
Throwable throwable = new ThreadDeath();
21+
throwable.setStackTrace(new StackTraceElement[0]);
22+
stopThread(thread, throwable);
23+
}
24+
}

libdt/Makefile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
LIB=libdt-$(OS)-$(ARCH).so
2+
CXXFLAGS=-O3 -fno-exceptions -fvisibility=hidden
3+
INCLUDES=-I$(JAVA_HOME)/include
4+
LIBS=-ldl
5+
SOURCES := $(wildcard src/*.c)
6+
7+
ifeq ($(JAVA_HOME),)
8+
export JAVA_HOME:=$(shell java -cp . JavaHome)
9+
endif
10+
11+
ARCH:=$(shell uname -m)
12+
ifeq ($(ARCH),x86_64)
13+
ARCH=x64
14+
else
15+
ifeq ($(findstring arm,$(ARCH)),arm)
16+
ifeq ($(findstring 64,$(ARCH)),64)
17+
ARCH=arm64
18+
else
19+
ARCH=arm32
20+
endif
21+
else ifeq ($(findstring aarch64,$(ARCH)),aarch64)
22+
ARCH=arm64
23+
else
24+
ARCH=x86
25+
endif
26+
endif
27+
28+
OS:=$(shell uname -s)
29+
ifeq ($(OS),Darwin)
30+
CXXFLAGS += -arch x86_64 -arch arm64 -mmacos-version-min=10.12
31+
INCLUDES += -I$(JAVA_HOME)/include/darwin
32+
OS=macos
33+
ARCH=universal
34+
else
35+
CXXFLAGS += -Wl,-z,defs
36+
LIBS += -lrt
37+
INCLUDES += -I$(JAVA_HOME)/include/linux
38+
OS=linux
39+
CC=gcc
40+
endif
41+
42+
all: build/$(LIB)
43+
44+
clean:
45+
$(RM) -r build
46+
47+
build/$(LIB): $(SOURCES)
48+
mkdir -p build
49+
$(CC) $(CXXFLAGS) $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
113 KB
Binary file not shown.

libdt/src/dt_agent.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Native agent that nREPL uses for deeply buried JDK functionality.
2+
// Currently it is used to bring back Thread.stop() that was lost in JDK20+.
3+
4+
#include <jvmti.h>
5+
#include <stdio.h>
6+
7+
static jvmtiEnv* jvmti_env;
8+
9+
// https://docs.oracle.com/en/java/javase/21/docs/specs/jvmti.html#StopThread
10+
JNIEXPORT void JNICALL Java_dt_JvmtiAgent_stopThread
11+
(JNIEnv* env, jclass cls, jthread thread, jobject throwable) {
12+
jvmtiError err;
13+
jvmtiThreadInfo threadInfo;
14+
15+
err = (*jvmti_env)->GetThreadInfo(jvmti_env, thread, &threadInfo);
16+
if (err != JVMTI_ERROR_NONE) {
17+
printf("Error getting thread info: %d\n", err);
18+
return;
19+
}
20+
21+
//printf("Stopping thread \"%s\" using JVMTI...\n", threadInfo.name);
22+
23+
err = (*jvmti_env)->StopThread(jvmti_env, thread, throwable);
24+
if (err != JVMTI_ERROR_NONE) {
25+
printf("Error stopping thread: %d\n", err);
26+
return;
27+
}
28+
}
29+
30+
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* _) {
31+
//printf("nREPL native agent loaded\n");
32+
33+
// Initialize JVMTI environment.
34+
jint res = (*vm)->GetEnv(vm, (void**)&jvmti_env, JVMTI_VERSION_1_2);
35+
if (res != JNI_OK) {
36+
fprintf(stderr, "Failed to get JVMTI environment\n");
37+
return JNI_ERR;
38+
}
39+
40+
// Request capabilities for StopThread
41+
jvmtiCapabilities capabilities = {0};
42+
capabilities.can_signal_thread = 1;
43+
(*jvmti_env)->AddCapabilities(jvmti_env, &capabilities);
44+
45+
return JNI_OK;
46+
}

src/eval/deps_try.clj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,11 @@
111111
["--recipe-ns" recipe-location]
112112
["--recipe" recipe-location]))
113113
prepare (conj "-P"))]
114-
(apply run-repl "-Sdeps" (str {:paths paths
115-
:deps deps})
114+
(apply run-repl
115+
"-J-Djdk.attach.allowAttachSelf"
116+
"-J-XX:+EnableDynamicAgentLoading"
117+
"-Sdeps" (str {:paths paths
118+
:deps deps})
116119
"-M" "-m" "eval.deps-try.try" main-args)))
117120

118121
(defn- recipe-manifest-contents [{:keys [refresh] :as _cli-opts}]

src/eval/deps_try/try.clj

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
[eval.deps-try.history :as history]
1010
[eval.deps-try.recipe :as recipe]
1111
[eval.deps-try.rr-service :as rebel-service]
12+
[eval.deps-try.util :as util]
1213
[rebel-readline.clojure.line-reader :as clj-line-reader]
1314
[rebel-readline.clojure.main :as rebel-main]
1415
[rebel-readline.commands :as rebel-readline]
@@ -112,7 +113,11 @@
112113
(defn- handle-sigint-form
113114
[]
114115
`(let [thread# (Thread/currentThread)]
115-
(clj-repl/set-break-handler! (fn [_signal#] (.stop thread#)))))
116+
(clj-repl/set-break-handler!
117+
(fn [_signal#]
118+
(if (<= util/java-version 19)
119+
(.stop thread#)
120+
((requiring-resolve 'eval.deps-try.util.jvmti/stop-thread) thread#))))))
116121

117122
(defn- recipe-instructions [{:keys [ns-only]}]
118123
(str "Recipe" (when ns-only " namespace") " successfully loaded in the REPL-history. Press arrow-up to start with the first step."))
@@ -191,8 +196,12 @@
191196
repl-opts (cond-> {:deps-try/version (:version opts)
192197
:deps-try/data-path data-path
193198
:caught (fn [ex]
194-
(persist-just-caught ex)
195-
(clojure.main/repl-caught ex))
199+
(let [break-handled?
200+
(= 'java.lang.ThreadDeath
201+
(get-in (Throwable->map ex) [:via 1 :type]))]
202+
(when-not break-handled?
203+
(persist-just-caught ex)
204+
(clojure.main/repl-caught ex))))
196205
:init (fn []
197206
(load-slow-deps!)
198207
(apply require clojure.main/repl-requires)
@@ -210,6 +219,6 @@
210219

211220
(comment
212221

213-
(recipe/parse "/Users/gert/projects/deps-try/deps-try-recipes/recipes/next-jdbc/postgresql.clj")
222+
(recipe/parse "/Users/gert/projects/deps-try/deps-try/recipes/next_jdbc/postgresql.clj")
214223

215224
#_:end)

0 commit comments

Comments
 (0)