From 57a3fba8a55621266be1d51361540b6f4ac4965e Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Thu, 16 Jan 2025 19:04:43 +0100 Subject: [PATCH 1/8] Clojure 1.12.0 --- src/eval/deps_try.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eval/deps_try.clj b/src/eval/deps_try.clj index 12d84582..9114237e 100644 --- a/src/eval/deps_try.clj +++ b/src/eval/deps_try.clj @@ -103,7 +103,7 @@ (let [paths (into ["."] ;; needed for clojure.java.io/resource (string/split init-cp #":")) deps (merge - {'org.clojure/clojure {:mvn/version "1.12.0-alpha11"}} + {'org.clojure/clojure {:mvn/version "1.12.0"}} recipe-deps requested-deps) main-args (cond-> ["--version" version] From 4e407febff36a9f44d173c6ed5857d25c889818c Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Thu, 16 Jan 2025 21:58:48 +0100 Subject: [PATCH 2/8] Handle breaks for Java > 19 --- .github/workflows/release.yml | 12 ++++-- .gitignore | 1 + bb.edn | 6 ++- deps.edn | 6 +-- java/dt/JvmtiAgent.java | 24 ++++++++++++ libdt/Makefile | 49 +++++++++++++++++++++++++ libdt/build/libdt-linux-arm64.so | Bin 0 -> 70096 bytes libdt/build/libdt-linux-x64.so | Bin 0 -> 15800 bytes libdt/build/libdt-macos-universal.so | Bin 0 -> 115664 bytes libdt/src/dt_agent.c | 46 +++++++++++++++++++++++ src/eval/deps_try.clj | 7 +++- src/eval/deps_try/try.clj | 17 +++++++-- src/eval/deps_try/util.clj | 13 +++++++ src/eval/deps_try/util/jvmti.clj | 53 +++++++++++++++++++++++++++ 14 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 java/dt/JvmtiAgent.java create mode 100644 libdt/Makefile create mode 100755 libdt/build/libdt-linux-arm64.so create mode 100755 libdt/build/libdt-linux-x64.so create mode 100755 libdt/build/libdt-macos-universal.so create mode 100644 libdt/src/dt_agent.c create mode 100644 src/eval/deps_try/util/jvmti.clj diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e5fb1074..191abb1d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ on: push: branches: - master - - feature/dockerfile + - fix/break-handling jobs: Config: @@ -67,14 +67,20 @@ jobs: bb: latest - name: Update VERSION run: echo "${{ needs.Config.outputs.version }}" > resources/VERSION + - name: Compile Java + run: bb java:compile - name: Zip it run: zip -r deps-try.zip . -x ".git/*" - name: Build uberjar run: | - rm -rf .git + # remove stuff not wanted in JAR + rm -rf .git .github mv deps-try.zip .. - bb uberjar deps-try-bb.jar -m eval.deps-try + + bb bb:uberjar + ls -lah deps-try-bb.jar jar -tf deps-try-bb.jar + mv ../deps-try.zip . - name: Testrun uberjar run: bb deps-try-bb.jar -h diff --git a/.gitignore b/.gitignore index d99d61a8..eaae4ada 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /.clj-kondo .lsp /VERSION +*.class diff --git a/bb.edn b/bb.edn index 18eca27c..4b0ef685 100644 --- a/bb.edn +++ b/bb.edn @@ -1,7 +1,11 @@ {:deps {io.github.eval/deps-try {:local/root "."}} :bbin/bin {deps-try {:main-opts ["-m" "eval.deps-try"]}} :tasks - {gen-manifest {:doc "Generate recipe-manifest and print to stdout" + {java:compile {:doc "Compile files under java/" + :task (shell {:dir "java"} "javac --release 11 dt/JvmtiAgent.java")} + bb:uberjar {:doc "Create the app's uberjar" + :task (shell "bb uberjar deps-try-bb.jar -m eval.deps-try")} + gen-manifest {:doc "Generate recipe-manifest and print to stdout" :requires ([babashka.fs :as fs]) :task (exec 'eval.deps-try.recipe/generate&print-manifest {:exec-args diff --git a/deps.edn b/deps.edn index c2e3b840..011d2dbb 100644 --- a/deps.edn +++ b/deps.edn @@ -1,11 +1,11 @@ -{:paths ["src" "resources" "."] +{:paths ["src" "resources" "." "java" "libdt/build"] :deps {borkdude/edamame {:mvn/version "1.4.24"} - cider/orchard {:mvn/version "0.22.0"} + cider/orchard {:mvn/version "0.30.0"} com.bhauman/rebel-readline {:local/root "./vendor/rebel-readline/rebel-readline"} deps-try/cli {:local/root "./vendor/deps-try.cli"} deps-try/http-client {:local/root "./vendor/deps-try.http-client"} io.github.eval/compliment {:git/sha "cd9706db27d456e8940dcd1118174c94effa9358"} - org.clojure/clojure {:mvn/version "1.12.0-alpha11"} + org.clojure/clojure {:mvn/version "1.12.0"} org.clojure/tools.gitlibs {:mvn/version "2.5.197"} ;; suppress logging org.slf4j/slf4j-nop {:mvn/version "2.0.5"}} diff --git a/java/dt/JvmtiAgent.java b/java/dt/JvmtiAgent.java new file mode 100644 index 00000000..5c026cec --- /dev/null +++ b/java/dt/JvmtiAgent.java @@ -0,0 +1,24 @@ +package dt; + +/** + * Java facade for the C side of the libnrepl JVMTI agent. Currently used to + * stop threads on JDK20+. + */ +public class JvmtiAgent { + + // This will bind to Java_nrepl_JvmtiAgent_stopThread function once the + // agent containing this function will be attached. + public static native void stopThread(Thread thread, Throwable throwable); + + /** + * Forcibly stop a given thread. + */ + public static void stopThread(Thread thread) { + // ThreadDeath is deprecated, but so it Thread.stop(). We can revisit + // this when JVM actually decides to remove this class. + @SuppressWarnings("deprecation") + Throwable throwable = new ThreadDeath(); + throwable.setStackTrace(new StackTraceElement[0]); + stopThread(thread, throwable); + } +} diff --git a/libdt/Makefile b/libdt/Makefile new file mode 100644 index 00000000..528fe3e3 --- /dev/null +++ b/libdt/Makefile @@ -0,0 +1,49 @@ +LIB=libdt-$(OS)-$(ARCH).so +CXXFLAGS=-O3 -fno-exceptions -fvisibility=hidden +INCLUDES=-I$(JAVA_HOME)/include +LIBS=-ldl +SOURCES := $(wildcard src/*.c) + +ifeq ($(JAVA_HOME),) + export JAVA_HOME:=$(shell java -cp . JavaHome) +endif + +ARCH:=$(shell uname -m) +ifeq ($(ARCH),x86_64) + ARCH=x64 +else + ifeq ($(findstring arm,$(ARCH)),arm) + ifeq ($(findstring 64,$(ARCH)),64) + ARCH=arm64 + else + ARCH=arm32 + endif + else ifeq ($(findstring aarch64,$(ARCH)),aarch64) + ARCH=arm64 + else + ARCH=x86 + endif +endif + +OS:=$(shell uname -s) +ifeq ($(OS),Darwin) + CXXFLAGS += -arch x86_64 -arch arm64 -mmacos-version-min=10.12 + INCLUDES += -I$(JAVA_HOME)/include/darwin + OS=macos + ARCH=universal +else + CXXFLAGS += -Wl,-z,defs + LIBS += -lrt + INCLUDES += -I$(JAVA_HOME)/include/linux + OS=linux + CC=gcc +endif + +all: build/$(LIB) + +clean: + $(RM) -r build + +build/$(LIB): $(SOURCES) + mkdir -p build + $(CC) $(CXXFLAGS) $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS) diff --git a/libdt/build/libdt-linux-arm64.so b/libdt/build/libdt-linux-arm64.so new file mode 100755 index 0000000000000000000000000000000000000000..a1fd9f935cf0e803f7af0e59509e151673a7ba6e GIT binary patch literal 70096 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 F{{;$0KivQT literal 0 HcmV?d00001 diff --git a/libdt/build/libdt-linux-x64.so b/libdt/build/libdt-linux-x64.so new file mode 100755 index 0000000000000000000000000000000000000000..c9370f16b2c6fe1c731d47051d720b079e4e2df7 GIT binary patch literal 15800 zcmeHOeQX>@6`woX&E=!#qol-1AlU?`a!7Nz#Bp#_g`7XL2~J`X2ZCC(J^S{2*WQP< zx98aXqeL}rIk}OHp#2B_NJS6`iAb%41O=5#6Ga7D5x%5SR1_7pIHjVFQmPb0miK1f zTdz0gf{GAA+Oclmyx+Xv%-grKce9?iU+f#`4~Ig6ON027nAe0zQbXr1wm_^?EQjMd z@iA%JINx+rt@9QIu@o_-;}}g?CEYKd3M)I7+>Sgso~GJ`Pfn2YEWJE*Wp1nD&Fxtz z6Et3uB=b;}rw~{65FS-X)@VGI5o$&s@$(((&o5iajwRY+L5ZK^8&JDhY7!OhY4dnT zU$@gAgf~dMd#Pi;+)3lHWIsOxJi4@G@&1A@^AQ?vvFi|{c(J7L%9yN#AsXPb@6a*v z(~tjd>)7Al`E=xIt@p93>o&B%`K{&X0n(Sk2YKk9CPGOeRFn|^(&xT+xcx%QW~kOJ z=%-evEZ>bDpp6DcE; zE2i?f2TgGxHJ&okwsBy*VCT9<&7uwcm&S&&mYGTm!x*!2MLT1RWFG(pMjm+p>qaJ( z%L`?GxY%U_o)u*qoUugagq5>Rkj6A^F!J&H~Ewv9cxy)1p!QfG~SC>J8!o=__+N2xeGZ0v!u~Z}5FLNb$T*-zSX6 z2yaPB8qGiIL=X=LtI|);5gU0T=L-F`|f2AzJ!fIUiRSZlWa~ZBebPkO#%@Q z&i6Pr#5}nCzD6)8K?H&b1Q7@#5JVt|KoEf-0{^!WxYoSxZ~D|*5q-MphuehEAF0`4 z=aN2kA#z@xlhg4tfSpY*!m)XMQlNbnTdut3I8J6p>bQWt@~e3r7nWC^cXek|5!jjL z^}Ry!olTGW^oM-)`5A=W_Gm~lBmc??Xe577KXz2F{#vj80pi>VojeVs zv_5_3rZ(AgzO_xSzNA-Q)2Ej>gULr~m3A-1G~Ymci{gg)$^I=w`+R!jpogbt8x0%w*xY0bOjFcX1oI zs8wIS=lru8p6hf|0qD=+bb;&i6g*h{2qNX^(~;RmbnD#gtN(BuIY;E2bY~$Df)Yd^ zh(Hj5AOb-If(Qf=2qF+fAc#N^fgl3^Q3U!dt7K`Trfuhnqna&u=(JohQ`)Un#(i1Ki#lo9{u9Lu(uEPJnl~Z*<{p#~A?G zIOjMv$dw=;NByefoB??TB<``{9xs;AgGWSYA{JV^Vo78M9vE8z`zU6YZ@V5k@{d4vQ~?9q;imelJu`0**d$!Dn#v=Q#Ky_eA@mu}8yQ z(Uz%(1JS|o;Ie2-S2WfYjr1=U8rYqN&x>Fy_f^4mDm)lXgbyu?#(JWWF1ZB_7HSV4 z$H#NW{c3nBufg-N+`k49lpq2@1cC?z5eOm>L?DPj5P={9K?H&b{MRGE`HGyMhYn05onsm;CTuVCV7_K3m^C9t>4GXvb^@dYI{St0dLUD5n@!<#x z=Qduac6bDp{z9Z3uPVM|W$+?lM2%n>Y8BdVTe%8nibB3|-c(6?= z{huR$Vf#|*97lLGl=>^w{vPSKKf7s3MW&8=K)bszev|laSl2C!d^JcO0 z{Vv#qAY*$FPsCU1^^f_;VG$L4-!bJmsDxlP^7Uc-=MukNxbG)W!*8UF3C`%YetH@3di_5v z?L7lh_3Mk^XBX+`1;97n+>Q7n;FQ9>uyw?;%XTG`iI0f+G&6*S$D^)O!bfr+31d2y9 zz|vk|3aR!f%(4fc?#GmS=wTc<{YSbE_8EPLdNHpb`^TZnB_o?ErZJtr_w$Fk4({&( z6#heH^yx&el}!@u#y!UB?dL$a?m9STmBviVo>bkc8KgSlI*gtw z7r|040b$&S}k zEb;uXARpIUe*Oet7!zLaxxXv)`@-u=Fp&j)^_DUyUcN>=mPxNM)cI$ND=5e=A`uMy~V9D!tw)e*m_d0Nh9c-Xc zg(U8kcne=YcIyl@Vx8v?uP<2c^pc_OAO9I2e@0UTmKT)j9(jBQehd}w_lN&|NsSFj z`uV>A9M_sWzxjEF-%Iq{v(EDOz-{*Md7XKED}YegPHFQxo)vigHoeF zbKIE6^Wb5yK@;Zl`((9k$QGha3HQi+mUGbJ<@0)PmiP&msVdB4`JRu@>(Le>vK@Eh z*YSY>G4@|eDoc&{F{QdkKfe*2NB{kPGXD;jsVZoT#VdL4sup$Bsl!=n#OGI7RlC=d lzyI;MkbPH7bxgD?`7RGcjXqVbE$i{`=um`PeFUE>{t2z+d1n9s literal 0 HcmV?d00001 diff --git a/libdt/build/libdt-macos-universal.so b/libdt/build/libdt-macos-universal.so new file mode 100755 index 0000000000000000000000000000000000000000..ef78fc3602304a63604b687ea0b5c555daf7a453 GIT binary patch literal 115664 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 +#include + +static jvmtiEnv* jvmti_env; + +// https://docs.oracle.com/en/java/javase/21/docs/specs/jvmti.html#StopThread +JNIEXPORT void JNICALL Java_dt_JvmtiAgent_stopThread +(JNIEnv* env, jclass cls, jthread thread, jobject throwable) { + jvmtiError err; + jvmtiThreadInfo threadInfo; + + err = (*jvmti_env)->GetThreadInfo(jvmti_env, thread, &threadInfo); + if (err != JVMTI_ERROR_NONE) { + printf("Error getting thread info: %d\n", err); + return; + } + + //printf("Stopping thread \"%s\" using JVMTI...\n", threadInfo.name); + + err = (*jvmti_env)->StopThread(jvmti_env, thread, throwable); + if (err != JVMTI_ERROR_NONE) { + printf("Error stopping thread: %d\n", err); + return; + } +} + +JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* _) { + //printf("nREPL native agent loaded\n"); + + // Initialize JVMTI environment. + jint res = (*vm)->GetEnv(vm, (void**)&jvmti_env, JVMTI_VERSION_1_2); + if (res != JNI_OK) { + fprintf(stderr, "Failed to get JVMTI environment\n"); + return JNI_ERR; + } + + // Request capabilities for StopThread + jvmtiCapabilities capabilities = {0}; + capabilities.can_signal_thread = 1; + (*jvmti_env)->AddCapabilities(jvmti_env, &capabilities); + + return JNI_OK; +} diff --git a/src/eval/deps_try.clj b/src/eval/deps_try.clj index 9114237e..e2d9869a 100644 --- a/src/eval/deps_try.clj +++ b/src/eval/deps_try.clj @@ -111,8 +111,11 @@ ["--recipe-ns" recipe-location] ["--recipe" recipe-location])) prepare (conj "-P"))] - (apply run-repl "-Sdeps" (str {:paths paths - :deps deps}) + (apply run-repl + "-J-Djdk.attach.allowAttachSelf" + "-J-XX:+EnableDynamicAgentLoading" + "-Sdeps" (str {:paths paths + :deps deps}) "-M" "-m" "eval.deps-try.try" main-args))) (defn- recipe-manifest-contents [{:keys [refresh] :as _cli-opts}] diff --git a/src/eval/deps_try/try.clj b/src/eval/deps_try/try.clj index daf55352..f2d9e279 100644 --- a/src/eval/deps_try/try.clj +++ b/src/eval/deps_try/try.clj @@ -9,6 +9,7 @@ [eval.deps-try.history :as history] [eval.deps-try.recipe :as recipe] [eval.deps-try.rr-service :as rebel-service] + [eval.deps-try.util :as util] [rebel-readline.clojure.line-reader :as clj-line-reader] [rebel-readline.clojure.main :as rebel-main] [rebel-readline.commands :as rebel-readline] @@ -112,7 +113,11 @@ (defn- handle-sigint-form [] `(let [thread# (Thread/currentThread)] - (clj-repl/set-break-handler! (fn [_signal#] (.stop thread#))))) + (clj-repl/set-break-handler! + (fn [_signal#] + (if (<= util/java-version 19) + (.stop thread#) + ((requiring-resolve 'eval.deps-try.util.jvmti/stop-thread) thread#)))))) (defn- recipe-instructions [{:keys [ns-only]}] (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 @@ repl-opts (cond-> {:deps-try/version (:version opts) :deps-try/data-path data-path :caught (fn [ex] - (persist-just-caught ex) - (clojure.main/repl-caught ex)) + (let [break-handled? + (= 'java.lang.ThreadDeath + (get-in (Throwable->map ex) [:via 1 :type]))] + (when-not break-handled? + (persist-just-caught ex) + (clojure.main/repl-caught ex)))) :init (fn [] (load-slow-deps!) (apply require clojure.main/repl-requires) @@ -210,6 +219,6 @@ (comment - (recipe/parse "/Users/gert/projects/deps-try/deps-try-recipes/recipes/next-jdbc/postgresql.clj") + (recipe/parse "/Users/gert/projects/deps-try/deps-try/recipes/next_jdbc/postgresql.clj") #_:end) diff --git a/src/eval/deps_try/util.clj b/src/eval/deps_try/util.clj index d0745c44..e925857a 100644 --- a/src/eval/deps_try/util.clj +++ b/src/eval/deps_try/util.clj @@ -4,6 +4,19 @@ [eval.deps-try.fs :as fs] [eval.deps-try.process :as p])) +(defn parse-java-version + "Parse Java version string according to JEP 223 and return version as a number." + [] + (try (let [s (System/getProperty "java.specification.version") + [major minor _] (string/split s #"\.") + major (Integer/parseInt major)] + (if (> major 1) + major + (Integer/parseInt minor))) + (catch Exception _ 8))) + +(def java-version "Current Java version number." (parse-java-version)) + (defn duration->millis [{:keys [seconds minutes hours days weeks] :or {seconds 0 minutes 0 hours 0 days 0 weeks 0}}] (let [days (+ days (* weeks 7)) diff --git a/src/eval/deps_try/util/jvmti.clj b/src/eval/deps_try/util/jvmti.clj new file mode 100644 index 00000000..ea00cb83 --- /dev/null +++ b/src/eval/deps_try/util/jvmti.clj @@ -0,0 +1,53 @@ +(ns eval.deps-try.util.jvmti + "This namespace contains code exclusive to JDK9+ and should not be attempted to + load with earlier JDKs." + (:require + [clojure.java.io :as io]) + (:import + (com.sun.tools.attach VirtualMachine) + (java.lang ProcessHandle) + (java.nio.file Files) + (java.nio.file.attribute FileAttribute) + (dt JvmtiAgent))) + +;;; Agent unpacking + +(defonce ^:private temp-directory + (.toFile (Files/createTempDirectory "deps_try" (into-array FileAttribute [])))) + +(defn- unpack-from-jar [resource-name] + (let [path (io/file temp-directory resource-name)] + (if-let [resource (io/resource resource-name)] + (io/copy (io/input-stream resource) path) + (throw (ex-info (str "Could not find " resource-name " in resources.") {}))) + (.getAbsolutePath path))) + +(defn- macos? [] + (re-find #"(?i)mac" (System/getProperty "os.name"))) + +(defn- aarch64? [] + (re-find #"(?i)aarch64" (System/getProperty "os.arch"))) + +(def ^:private libdt-path + (delay + (let [lib (cond (macos?) "libdt-macos-universal.so" + (aarch64?) "libdt-linux-arm64.so" + :else "libdt-linux-x64.so")] + (unpack-from-jar lib)))) + +;;; Agent loading + +(defn- attach-self ^VirtualMachine [] + (VirtualMachine/attach (str (.pid (ProcessHandle/current))))) + +(defn- load-libdt-agent [] + (.loadAgentPath (attach-self) @libdt-path)) + +(def ^:private agent-loaded (delay (load-libdt-agent))) + +(defn stop-thread + "Stop the given `thread` using JVMTI StopThread function. Risks state + corruption. Should not be used prior to JDK20." + [thread] + @agent-loaded + (JvmtiAgent/stopThread thread)) From 19df88efd088b49e729b29607b9c24fc5cedad6e Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Fri, 3 Jul 2026 17:08:45 +0200 Subject: [PATCH 3/8] Remove warning on boot WARNING: Use of :paths external to the project has been deprecated... --- src/eval/deps_try.clj | 17 +++++++++++++++-- src/eval/deps_try/deps.cljc | 3 +++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/eval/deps_try.clj b/src/eval/deps_try.clj index e2d9869a..e609f84e 100644 --- a/src/eval/deps_try.clj +++ b/src/eval/deps_try.clj @@ -100,10 +100,23 @@ ;; TODO re-enable? ;; see https://github.com/clojure/brew-install/blob/1.11.3/CHANGELOG.md #_(warn-unless-minimum-clojure-cli-version "1.11.1.1273" tdeps-version) - (let [paths (into ["."] ;; needed for clojure.java.io/resource - (string/split init-cp #":")) + (let [cp-parts (string/split init-cp #":") + ;; When running from the packaged uberjar, its path is external to the + ;; project and tools.deps deprecates such :paths entries, so it's added as + ;; a :local/root dep instead. In dev the classpath is the project's own + ;; source dirs and resolved deps, which belong in :paths as before. + ;; "." is kept for clojure.java.io/resource. + {jars true, dirs false} (if dev? + {false cp-parts} + (group-by #(string/ends-with? % ".jar") cp-parts)) + paths (into ["."] dirs) + self-deps (into {} (map-indexed (fn [i jar] + [(symbol "deps-try.self" (str "cp" i)) + {:local/root jar}]) + jars)) deps (merge {'org.clojure/clojure {:mvn/version "1.12.0"}} + self-deps recipe-deps requested-deps) main-args (cond-> ["--version" version] diff --git a/src/eval/deps_try/deps.cljc b/src/eval/deps_try/deps.cljc index ddd5eff6..9eaa67b9 100644 --- a/src/eval/deps_try/deps.cljc +++ b/src/eval/deps_try/deps.cljc @@ -370,6 +370,9 @@ (defn deps-available [] (->> (clojure.java.basis/current-basis) :libs + ;; synthetic deps used to put the packaged uberjar on the classpath + ;; (see eval.deps-try/start-repl!) shouldn't show as user libraries + (remove (comp #{"deps-try.self"} namespace key)) (filter (comp #(every? empty? %) :parents val))))) #?(:bb nil From 8d77dc6fc2b68a11858f1238cb359691f5249a2e Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Mon, 13 Jul 2026 19:10:13 +0200 Subject: [PATCH 4/8] Clojure 1.12.5 --- deps.edn | 2 +- src/eval/deps_try.clj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deps.edn b/deps.edn index 011d2dbb..377fe0b2 100644 --- a/deps.edn +++ b/deps.edn @@ -5,7 +5,7 @@ deps-try/cli {:local/root "./vendor/deps-try.cli"} deps-try/http-client {:local/root "./vendor/deps-try.http-client"} io.github.eval/compliment {:git/sha "cd9706db27d456e8940dcd1118174c94effa9358"} - org.clojure/clojure {:mvn/version "1.12.0"} + org.clojure/clojure {:mvn/version "1.12.5"} org.clojure/tools.gitlibs {:mvn/version "2.5.197"} ;; suppress logging org.slf4j/slf4j-nop {:mvn/version "2.0.5"}} diff --git a/src/eval/deps_try.clj b/src/eval/deps_try.clj index e609f84e..c121ada8 100644 --- a/src/eval/deps_try.clj +++ b/src/eval/deps_try.clj @@ -115,7 +115,7 @@ {:local/root jar}]) jars)) deps (merge - {'org.clojure/clojure {:mvn/version "1.12.0"}} + {'org.clojure/clojure {:mvn/version "1.12.5"}} self-deps recipe-deps requested-deps) From b66712a957e40076a9c8d87d68f9e3fe90340291 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Mon, 13 Jul 2026 19:26:23 +0200 Subject: [PATCH 5/8] Add tmp-folder --- .gitignore | 2 ++ tmp/.keep | 0 2 files changed, 2 insertions(+) create mode 100644 tmp/.keep diff --git a/.gitignore b/.gitignore index eaae4ada..e05d29d8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ .lsp /VERSION *.class +/tmp/* +!/tmp/.keep diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 00000000..e69de29b From 46bab8a43a2f8414716e3e29d5e258d7870660be Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Mon, 13 Jul 2026 21:27:33 +0200 Subject: [PATCH 6/8] Claude review --- java/dt/JvmtiAgent.java | 6 ++--- libdt/src/dt_agent.c | 5 ++-- src/eval/deps_try.clj | 36 ++++++++++++++++++++----- src/eval/deps_try/try.clj | 45 +++++++++++++++++++++++++------- src/eval/deps_try/util/jvmti.clj | 25 +++++++++++++----- 5 files changed, 91 insertions(+), 26 deletions(-) diff --git a/java/dt/JvmtiAgent.java b/java/dt/JvmtiAgent.java index 5c026cec..4b700259 100644 --- a/java/dt/JvmtiAgent.java +++ b/java/dt/JvmtiAgent.java @@ -1,12 +1,12 @@ package dt; /** - * Java facade for the C side of the libnrepl JVMTI agent. Currently used to + * Java facade for the C side of the libdt JVMTI agent. Currently used to * stop threads on JDK20+. */ public class JvmtiAgent { - // This will bind to Java_nrepl_JvmtiAgent_stopThread function once the + // This will bind to Java_dt_JvmtiAgent_stopThread function once the // agent containing this function will be attached. public static native void stopThread(Thread thread, Throwable throwable); @@ -14,7 +14,7 @@ public class JvmtiAgent { * Forcibly stop a given thread. */ public static void stopThread(Thread thread) { - // ThreadDeath is deprecated, but so it Thread.stop(). We can revisit + // ThreadDeath is deprecated, but so is Thread.stop(). We can revisit // this when JVM actually decides to remove this class. @SuppressWarnings("deprecation") Throwable throwable = new ThreadDeath(); diff --git a/libdt/src/dt_agent.c b/libdt/src/dt_agent.c index 5c39411d..fbbea31a 100644 --- a/libdt/src/dt_agent.c +++ b/libdt/src/dt_agent.c @@ -1,5 +1,6 @@ -// Native agent that nREPL uses for deeply buried JDK functionality. +// Native agent that deps-try uses for deeply buried JDK functionality. // Currently it is used to bring back Thread.stop() that was lost in JDK20+. +// Adapted from nREPL's libnrepl agent (https://github.com/nrepl/nrepl/pull/318). #include #include @@ -28,7 +29,7 @@ JNIEXPORT void JNICALL Java_dt_JvmtiAgent_stopThread } JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* _) { - //printf("nREPL native agent loaded\n"); + //printf("libdt native agent loaded\n"); // Initialize JVMTI environment. jint res = (*vm)->GetEnv(vm, (void**)&jvmti_env, JVMTI_VERSION_1_2); diff --git a/src/eval/deps_try.clj b/src/eval/deps_try.clj index c121ada8..a0082c95 100644 --- a/src/eval/deps_try.clj +++ b/src/eval/deps_try.clj @@ -26,6 +26,22 @@ (def ^:private dev? (nil? (io/resource "VERSION"))) +(defn- repl-java-version + "Major version of the JVM that the `clojure` CLI will start (found via + JAVA_CMD, JAVA_HOME or PATH, mirroring the CLI's lookup), nil when it can't + be determined." + [] + (let [java-cmd (or (System/getenv "JAVA_CMD") + (some-> (System/getenv "JAVA_HOME") (fs/path "bin" "java") str) + "java") + ;; `java -version` prints to stderr, e.g. `openjdk version "21.0.2" ...` + version-line (try (:err (p/sh {:err :string} java-cmd "-version")) + (catch Exception _ nil)) + [_ major minor] (some->> version-line (re-find #"version \"(\d+)(?:\.(\d+))?")) + major (some-> major parse-long)] + (when major + (if (> major 1) major (some-> minor parse-long))))) + (def ^:private version (string/trim (if dev? @@ -123,13 +139,21 @@ recipe-location (into (if ns-only ["--recipe-ns" recipe-location] ["--recipe" recipe-location])) - prepare (conj "-P"))] + prepare (conj "-P")) + ;; Flags allowing the JVMTI agent to be loaded (needed for breaks on + ;; Java 20+, see eval.deps-try.util.jvmti). The -XX flag (which also + ;; suppresses the JEP 451 warning) only exists on Java 9+; Java 8 + ;; refuses to boot on unrecognized -XX options, so skip it there. + java-major (repl-java-version) + jvm-flags (cond-> ["-J-Djdk.attach.allowAttachSelf"] + (or (nil? java-major) (>= java-major 9)) + (conj "-J-XX:+EnableDynamicAgentLoading"))] (apply run-repl - "-J-Djdk.attach.allowAttachSelf" - "-J-XX:+EnableDynamicAgentLoading" - "-Sdeps" (str {:paths paths - :deps deps}) - "-M" "-m" "eval.deps-try.try" main-args))) + (concat jvm-flags + ["-Sdeps" (str {:paths paths + :deps deps}) + "-M" "-m" "eval.deps-try.try"] + main-args)))) (defn- recipe-manifest-contents [{:keys [refresh] :as _cli-opts}] (let [remote-manifest-file "https://raw.githubusercontent.com/eval/deps-try/master/recipes/manifest.edn" diff --git a/src/eval/deps_try/try.clj b/src/eval/deps_try/try.clj index f2d9e279..9018338c 100644 --- a/src/eval/deps_try/try.clj +++ b/src/eval/deps_try/try.clj @@ -109,15 +109,30 @@ (pp/pprint x))))) +(def ^:private jvmti-stop-thread + ;; Resolved eagerly (see `:init` in `-main`): `requiring-resolve` takes + ;; Clojure's require-lock, which the eval being interrupted may itself hold + ;; (e.g. Ctrl-C during a hung require), deadlocking the break handler. + (delay (requiring-resolve 'eval.deps-try.util.jvmti/stop-thread))) + +(defn break-thread! + "Forcibly stop `thread`. Called from the SIGINT-handler thread; public as + it's referenced from an eval'ed form (see `handle-sigint-form`)." + [^Thread thread] + (try + (if (<= util/java-version 19) + (.stop thread) + (@jvmti-stop-thread thread)) + (catch Throwable e + (println (str "Break failed: " (ex-message e)))))) + ;; SOURCE https://github.com/bhauman/rebel-readline/pull/199 (defn- handle-sigint-form [] `(let [thread# (Thread/currentThread)] (clj-repl/set-break-handler! (fn [_signal#] - (if (<= util/java-version 19) - (.stop thread#) - ((requiring-resolve 'eval.deps-try.util.jvmti/stop-thread) thread#)))))) + (break-thread! thread#))))) (defn- recipe-instructions [{:keys [ns-only]}] (str "Recipe" (when ns-only " namespace") " successfully loaded in the REPL-history. Press arrow-up to start with the first step.")) @@ -177,6 +192,18 @@ [ex] (swap! api/*line-reader* assoc :repl/just-caught ex)) +(defn- interrupted? + "Returns true if the given throwable was ultimately caused by the break + handler stopping the thread (see `break-thread!`). + The ThreadDeath arrives bare when the eval was interrupted at runtime, or + wrapped in a CompilerException (where `clojure.main/root-cause` stops + unwrapping) when interrupted during compilation. + SOURCE `nrepl.middleware.interruptible-eval/interrupted?`" + [^Throwable ex] + (or (instance? ThreadDeath (clojure.main/root-cause ex)) + (and (instance? clojure.lang.Compiler$CompilerException ex) + (instance? ThreadDeath (.getCause ex))))) + (defn- reset-just-caught [] `(swap! api/*line-reader* dissoc :repl/just-caught)) @@ -196,14 +223,14 @@ repl-opts (cond-> {:deps-try/version (:version opts) :deps-try/data-path data-path :caught (fn [ex] - (let [break-handled? - (= 'java.lang.ThreadDeath - (get-in (Throwable->map ex) [:via 1 :type]))] - (when-not break-handled? - (persist-just-caught ex) - (clojure.main/repl-caught ex)))) + (when-not (interrupted? ex) + (persist-just-caught ex) + (clojure.main/repl-caught ex))) :init (fn [] (load-slow-deps!) + (when (>= util/java-version 20) + ;; see comment at jvmti-stop-thread + (try @jvmti-stop-thread (catch Throwable _))) (apply require clojure.main/repl-requires) (set! clojure.core/*print-namespace-maps* false)) :eval (fn [form] diff --git a/src/eval/deps_try/util/jvmti.clj b/src/eval/deps_try/util/jvmti.clj index ea00cb83..3f2dcebb 100644 --- a/src/eval/deps_try/util/jvmti.clj +++ b/src/eval/deps_try/util/jvmti.clj @@ -13,26 +13,37 @@ ;;; Agent unpacking (defonce ^:private temp-directory - (.toFile (Files/createTempDirectory "deps_try" (into-array FileAttribute [])))) + ;; deleteOnExit runs in reverse order of registration: the unpacked lib + ;; (registered later) is deleted first, leaving the directory empty. + (doto (.toFile (Files/createTempDirectory "deps_try" (into-array FileAttribute []))) + (.deleteOnExit))) (defn- unpack-from-jar [resource-name] (let [path (io/file temp-directory resource-name)] (if-let [resource (io/resource resource-name)] - (io/copy (io/input-stream resource) path) + (with-open [in (io/input-stream resource)] + (io/copy in path)) (throw (ex-info (str "Could not find " resource-name " in resources.") {}))) + (.deleteOnExit path) (.getAbsolutePath path))) (defn- macos? [] (re-find #"(?i)mac" (System/getProperty "os.name"))) +(defn- linux? [] + (re-find #"(?i)linux" (System/getProperty "os.name"))) + (defn- aarch64? [] (re-find #"(?i)aarch64" (System/getProperty "os.arch"))) (def ^:private libdt-path (delay - (let [lib (cond (macos?) "libdt-macos-universal.so" - (aarch64?) "libdt-linux-arm64.so" - :else "libdt-linux-x64.so")] + (let [os (System/getProperty "os.name") + lib (cond (macos?) "libdt-macos-universal.so" + (and (linux?) (aarch64?)) "libdt-linux-arm64.so" + (linux?) "libdt-linux-x64.so" + :else (throw (ex-info (str "no native agent bundled for " os + " (only Linux and macOS)") {:os os})))] (unpack-from-jar lib)))) ;;; Agent loading @@ -41,7 +52,9 @@ (VirtualMachine/attach (str (.pid (ProcessHandle/current))))) (defn- load-libdt-agent [] - (.loadAgentPath (attach-self) @libdt-path)) + (doto (attach-self) + (.loadAgentPath @libdt-path) + (.detach))) (def ^:private agent-loaded (delay (load-libdt-agent))) From 1bc8b876d43b08cd5e7007837d41424d38db03f9 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Mon, 13 Jul 2026 21:51:47 +0200 Subject: [PATCH 7/8] Prevent unwanted files in jar This saves ~200KB of - test/, tmp/, docker/, recipes/, vendor/, - the duplicated src/ tree, - the libdt/ source tree, - README/CHANGELOG/bb.edn/deps.edn --- deps.edn | 5 ++++- src/eval/deps_try.clj | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/deps.edn b/deps.edn index 377fe0b2..e1b2956e 100644 --- a/deps.edn +++ b/deps.edn @@ -1,4 +1,7 @@ -{:paths ["src" "resources" "." "java" "libdt/build"] +;; NOTE keep "." out of :paths: `bb uberjar` jars every classpath folder +;; wholesale, so "." would put the entire repo in the released jar (and the +;; libdt binaries in twice). +{:paths ["src" "resources" "java" "libdt/build"] :deps {borkdude/edamame {:mvn/version "1.4.24"} cider/orchard {:mvn/version "0.30.0"} com.bhauman/rebel-readline {:local/root "./vendor/rebel-readline/rebel-readline"} diff --git a/src/eval/deps_try.clj b/src/eval/deps_try.clj index a0082c95..b943ce0b 100644 --- a/src/eval/deps_try.clj +++ b/src/eval/deps_try.clj @@ -45,7 +45,10 @@ (def ^:private version (string/trim (if dev? - (let [git-dir (fs/file (io/resource ".git"))] + ;; the repo-root isn't on the classpath (see NOTE in deps.edn), so derive + ;; it from a source file that is + (let [src-file (fs/file (io/resource "eval/deps_try.clj")) + git-dir (fs/path (-> src-file fs/parent fs/parent fs/parent) ".git")] (:out (p/sh {} "git" "--git-dir" (str git-dir) "describe" "--tags"))) (slurp (io/resource "VERSION"))))) From e0f0517e0e993f633e0b7d19ba6340a068910c23 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Mon, 13 Jul 2026 22:07:10 +0200 Subject: [PATCH 8/8] Fixes for binaries ...and have separate workflow for building these. --- .github/workflows/libdt.yml | 73 +++++++++++++++++++++++++++ libdt/Makefile | 2 +- libdt/build/libdt-linux-arm64.so | Bin 70096 -> 70096 bytes libdt/build/libdt-linux-x64.so | Bin 15800 -> 15800 bytes libdt/build/libdt-macos-universal.so | Bin 115664 -> 115664 bytes libdt/src/dt_agent.c | 10 ++-- 6 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/libdt.yml diff --git a/.github/workflows/libdt.yml b/.github/workflows/libdt.yml new file mode 100644 index 00000000..2d677de7 --- /dev/null +++ b/.github/workflows/libdt.yml @@ -0,0 +1,73 @@ +# Rebuilds the libdt native JVMTI agent binaries (libdt/build/*.so). +# Manually triggered; download the artifacts and commit them to libdt/build/. +# Adapted from nREPL's libnrepl workflow (https://github.com/nrepl/nrepl). +name: Build libdt native library + +on: + workflow_dispatch: + +jobs: + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Compile native library + run: cd libdt/ && make all + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: libdt-linux-x64 + path: libdt/build + + build-linux-arm64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + + - name: Run the build process with Docker + uses: addnab/docker-run-action@v3 + with: + image: eclipse-temurin:21 + options: | + --platform linux/arm64 + --volume ${{ github.workspace }}:/build + run: | + apt-get update && apt-get install -y make gcc + cd /build/libdt/ && make all + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: libdt-linux-arm64 + path: libdt/build + + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Compile native library + run: cd libdt/ && make all + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: libdt-macos-universal + path: libdt/build diff --git a/libdt/Makefile b/libdt/Makefile index 528fe3e3..53ac2a1a 100644 --- a/libdt/Makefile +++ b/libdt/Makefile @@ -5,7 +5,7 @@ LIBS=-ldl SOURCES := $(wildcard src/*.c) ifeq ($(JAVA_HOME),) - export JAVA_HOME:=$(shell java -cp . JavaHome) + $(error JAVA_HOME is not set (e.g. on macOS: JAVA_HOME=$$(/usr/libexec/java_home) make)) endif ARCH:=$(shell uname -m) diff --git a/libdt/build/libdt-linux-arm64.so b/libdt/build/libdt-linux-arm64.so index a1fd9f935cf0e803f7af0e59509e151673a7ba6e..2cb34f8d4d59192990f91ef2a8e184d956315774 100755 GIT binary patch delta 1104 zcmZ`(ZD>8Ai)m| z_<;jq2}eA0peR+LR)5rj5jULqGY0-x@rUeBA)WrH7+H1fN5J&@p58e9;oif!&wHNp z@V@Uo_q=y}NE;v0(%ojOo6TCQJG1|Ojo-9a?~u3Q(%jY!8}ICySZjVc^26XSCq5n7 zrqx~N1zg1`o>9E`iRY9M&eGG1d-BPBYTeQi3RT10?2OBfIc)>mS>`W0ZDNW?)U)Pb zBHBAJz{25u{BLzRU!=8lDohT>)G~tE;~{a?G<`Hh=Db5HCdrp36*)q=FU{Up*d!m; zhT!C#x|eU&=kR{L4+fvm|G&|$2KhIx^~m#4m;WP$ zeU_)NtPm>xRTU_LLUk}a2TMH)s{EC!G03``GK*+g%ThHxw7rd@JpaE;%%71uSy7O5 zlca0mtBiWS-RL_mk783K-q#!TCBE}TqEX+LU7cN$liEgJQ435Z1XLN%FmB_ zbzM6loh$qCwPke?{!SPp)Jm3hlyEoURl+3UUBd1e%cA$6wYNu)!#kLkT!{KP4cu)S zOWP^N$M0!YkiVNanM>MYngNo4g6iJ53A=Rt7d+ z)GnzVTseoY$9^`iz2f7gI*+^q(qIF7$cv>0B>DWA<0y<>n5j~c6ggm?TEWuN@xm`g zb=2xAt75TPH>)XV2kC+;LO7ZIUPTLVROB>TriGy+jJ$~JXkM_o+(qSVOkPn?pw(`1 zTgOU@F{vY@_)W3Qf&0dPEg@byDD#vsoM=E!1fB5e<5aVUM^K9w!b1cc;-KW~=;HZ^ zpPRkn#C7lq)f909?G@BB{R8oD6Z^tFu>;u)1`a_~yOBpB`_heHfj`A)1)7bdVV9Q_ z&nnR3Ntc&z@w`}7iDvU%>x=!BSYyV@zl4~Xy-|tZ6nr8MdeCA#GVHT02};uN1~=z1 AXaE2J delta 990 zcmY*YZAep57(UOvyK{HTt*JFdL8bhTOc-e@G_$avVtxOJP$_>X1+#(zi;U<;(2uPn z9U_#%vaI}3N+U3xu)-{81_k{HEc%hG$n`-Enz#3Ex1{&*@;vYJp7Wl2-*Zl+-xTRL z`CF`$R+h9$YoKE+V^mE=i^yxymsQf6vF~C1j?mDPBhw43@7}r^_2=@>xPb>eAi20- zGVvc$7B2A^MImVo9wk|UoL$yW1v8k9`7Ei*;S{hvhJGzAB}90ud{Js{=D*|$^ReTH z+KwLOg{HdgOfm*zGBL(rj49$v$qUFu;IdiI&k)y5pA#a~^SY(-5}VAh4TN)@&eu3sU)PIQeACS(>!bWPkIr&#|Q{kOPZJ-iONa$OAt z)Bt8mXquPMO?ZgVM|hGjOn8m(BjE_)$bzQL@HT6H*}dPIwuThl^lq&ppCqkb7sytU zd&uYbkhLTykJcOK$Vy2)MP5o;gnzM?xLy!T9&dx)v#HnACvV_V4-O1h*lvE~UYn0c z64}lWEzylSnY1Y$PvpbRa~94ZI(%ipA;T>ufMs$z^XkNf(NyeU^GOmkOQ;3SYLL+c zPw;|_YG8?&Fj43gs)A{7BL`Ggcambo%afwq)}Crn=z*T zyQd;%roAIVO+!8&3uhW!%3J#C0^W*Dd=d>LKoOm~?xKsS>si6;X&8X;t5_~Jsn{GG zP|*S5u;D$T!I%x>K##bej!M;SJTj6!pVIrDT)Z zPC?m^5c<)Bu-XVK@PQQtQ7jZ}Wl)f?VNem(7SwcZv#vip!=3MZ_k8!vxijZZ?~&dk zr*?~_lgNK7^($6!M9|2HzuPL_pc=X1f7Nd|m93E&!h32qZ5;8s<1i4L9aCY{VmQOJ zH8BK}a4~Ie|Bv#U-&gH!Px%*%--tO3 zP$r}r&ZB1}iG&%ow7u{iRd?W*?r=h**Im=lKq@P1p(!r6x%Oa#;;kVbWly!#SSKwY zuGK4+EUhe%3Zp?dq%DpBf+yQFRmpT=y|=g3iNrEnIgYyd9xdN;bD~Ir)~H^?Tzz zBG7!j+&TYe9wZwx^^0U*$qTRrKZLn73tTlC6HhZ1 zZ9&LlGjpS%_vLZdlAR^HEQy>lSj< z<2dnIRaYZEMf4&LBc9;=i>hAYap+(c>VTWfVyeK{*cZhRNyD)dhYVj?x}M>RJCK-d zNxh9;FBdFJN@*1;lJbQO0ax~cDexsPgZ6+ZnP~`eq-OdCu1M<)5ncz{WDSS|M5~oX zpxA1r!+~wqbb+_J&5Db0rd^aes67M_1=B42)2FTZBZA6Lkr$vl{Q@}c-$WqjJSNbeu&u;SE#NP)`~#0qMdAPe delta 1031 zcmYjQZ%9*76uy1Xdqf6hTG3&fVthT;Bbi-#Pc(bMAfTj`xlC zMGx|>Wh5=GSU}?AT!zGBafv@gGvrikc2eJJ&X63!JT)2jeo*{cbmXjytI%uFw+We; z9DN@OF;Vn`jA(N~C z`;&!Re^vGKt7zKZ`ky_*><vuqTc+KT=WTNZx{$Wtm!d`$tttVy47e0L{z1 zmjnT~}1g}OsRSm_Q116{`ir6bgS$nO>^{rQBh(bU;+KOKJ4UsQD$qBfL}tUObHemgpW9%1HIX>ZU7@#^rEn9Vv(&Fc^w+fmeQA~0dqGN77uaFg*I+? zqTk%J63cTzs;Zoh0WX^92y7Dj=^We@_vlls7L?@~AU5D7iKd`dGSRujKFPq*X6Ug? z7!@&w@|b5+~*610$s{c z-;s{v^^4tx9R~Lus}k=VmnodwI*(P^HKow$T+7d~E<>4vnA6FpvMdEjWVUS8Hqg7! zmu0C$i>rxp-zZ$I75S0u8yJCPEi2sUmiVP-*5Y>ZQ6ZgxL?MPO_dpX#_gRkqfPEe( J-3T#{-Ct<9E2${Su_*aNvYI~fn67uK-Q9O z*_w)6cbhdN3p$Mo{{UeTGL0)>rY=Tn3>Y_rp;57JM3U)X0qXbeZiz7=*-O6n-uJuT z_r2e}_lulNh@4CqI>i|xR***)QPzl6>g8C&BCDiH$^wh56h-)s3g@Ws9abuBbQGp!AD89S@%~?V-wnNdp5-#KFXn@W6e+z5_&|I>?xo|qtv_PLXADjk5rPGW zbnTe=)uZxf6no^anAj2rfD&?1LaB^X2_GU^mSDuqSPTd1(AQ5Ehm|#!yA`>yTzbjX z-P^Kpsn{FKjUiiX!3qBO4QpCbB&(+>?`gq#u-VSI+Ze%aXR7w?dA^cq6555Aod=z6 zXY=7QCfA+_QlDpAGJQM_%W2>XUGd}*ue9XJPE1rE#AF*T{?-=c(ocs=3ff)HLv9C% z@LEPH*XVfN<#ab=Mh9sGDjl>C>7-Lg2$LtaL^`~v6rcekSqH#yGy`arhQsNG8r2I1 zL{gB4&bb=SOWlnrW^y4V?~zi+o0HYE)l)}&?d+n5Exuo|(kNJ%^A1~=0U5+Df zmxgXwN$HEIay$OlkdAOGG_Gr51(k?qac3|Y!1PWs;()P*Tbb9I27cBW2Y}e7nT_g_ ztikJA-N0iZfh8IFlknYIho*nc2f@2t%y^pl00cFhxAp<}z(u=pp?6tg5{_DfB#xrq z+Dw2mc&#R#*IT>bA^rJS1)EhZmR&3JPeM?x+(^f&q9Y#r0C){+9lQEtlc9B%RFRZW zrHwS?-s4E5R8(M20EaEVFkrDv!>Xa#hw7w%j-JO6%H;L@tTe(;&l-~{n9zO{!2<8MZLa(27V-!``GoBdQ6 z{i5*1y4mqf*ZJf6C}kZmk4>*GOGb;~=KR#u52DT57c8huKUa6Sw`=8V$KPMY;9pvr BufYHS delta 1256 zcma)4Z%kWN6hF7Ep)%>(w-!pmT)T|XxIx+o-L&g_uR5~l)+lt&4-P2Z1UfR2b;T{A zvsGBI=`ekJ!?!;!bc@k2otE(d!v}e6#`q^jFa~gDk>O@w)<64z1DE=|@AdVAelVWo zo_o*l{O&pDo_i+y3MTss(y!Bmodj8K=QgmD+*z6=OjOQ(NP$fhcL&2(1>T^*R*HMv zLmFxoc&`H2j>J7BYW=E#!%bfmI%@g)&ld-$I!=$Iw_6@#>1J;7m@zts0GLDp)4ylZ z8~;PBqmK*W2bq4iRLIW{GYR*2j)0v^{JL0Y$9_Hb-@^X*rw(D^Bt9@bpIG*Kni{XQ zN>a6YU9Vih9wvU9>B;Ej_%$!%Kx3dn_FRxnOyU6-GE3RDI9?jx_}GHg7^cC(7B2CM z)nm&S*Y&%Y3~B%z0#2oGye5_}VU}2a4{LA#+GV_Xc8le+cuxkgg2kUiQ4H$1;^=-Z z6)n?A{iQK3E!@N}q8m{scQ?AO(4q9qMGZT?rdYb%vCE zc5Grxdao>A&M?tZl*_GT9qFBjken#SO&NENWLD^st>VtpE<&Tc0Jl;W4K;8Fk)|>Q zwkxn?t2npEW$-GV{Fr!0;|TfHdLs0F8+z1P)i+;!wBaN1%ILBNo!-jQ4Sdp#wz^XQ z)pKfC!SRw{ceASij42(xq6NH4>6M#m3?wFce9&$D~Zgu9+=8@qoM4R zz~*mR0n*lHU{!R?w;tXJ$+!6ciHuK~Nfv1&H0Cn^oC1gu*>2B}*aNJf_D^eLG2cQI zhHN+j6e)GoJb~)~R*Hi8z8s&QCeco$VF8sGj{a*LGTe7U z#s8TGu6Lr6Pfwuz+wy-6FNFPpww8`?WgzsTv%2b~t}yt0@72E%{h*@#?cXl4iFaGa zi6h?@{dM#Hjpx64%m3=F8UC58h_;-4`rQ1L(H~FP&bPUCy*9j*YOgt7>>e~7Uc7i} zAaF0se|QzeEamDB>zA|RpKUu+pJ1oQD#O949W{67W`gbapY6^Xf00~S5*gXIvuk!Ka-kvn9Q+MIL3JAd diff --git a/libdt/src/dt_agent.c b/libdt/src/dt_agent.c index fbbea31a..76f8bff6 100644 --- a/libdt/src/dt_agent.c +++ b/libdt/src/dt_agent.c @@ -15,7 +15,7 @@ JNIEXPORT void JNICALL Java_dt_JvmtiAgent_stopThread err = (*jvmti_env)->GetThreadInfo(jvmti_env, thread, &threadInfo); if (err != JVMTI_ERROR_NONE) { - printf("Error getting thread info: %d\n", err); + fprintf(stderr, "Error getting thread info: %d\n", err); return; } @@ -23,7 +23,7 @@ JNIEXPORT void JNICALL Java_dt_JvmtiAgent_stopThread err = (*jvmti_env)->StopThread(jvmti_env, thread, throwable); if (err != JVMTI_ERROR_NONE) { - printf("Error stopping thread: %d\n", err); + fprintf(stderr, "Error stopping thread: %d\n", err); return; } } @@ -41,7 +41,11 @@ JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* _) { // Request capabilities for StopThread jvmtiCapabilities capabilities = {0}; capabilities.can_signal_thread = 1; - (*jvmti_env)->AddCapabilities(jvmti_env, &capabilities); + jvmtiError err = (*jvmti_env)->AddCapabilities(jvmti_env, &capabilities); + if (err != JVMTI_ERROR_NONE) { + fprintf(stderr, "Failed to add JVMTI capabilities: %d\n", err); + return JNI_ERR; + } return JNI_OK; }