From 49b15275d771db3a627dfcb37ec10df45f1adbf0 Mon Sep 17 00:00:00 2001 From: Oleksandr Yakushev Date: Sun, 2 Jun 2024 23:08:12 +0300 Subject: [PATCH] Support adding -Djdk.attach.allowAttachSelf to jack-in params Add `cider-enable-nrepl-jvmti-agent` customizable to control this behaviour. --- CHANGELOG.md | 1 + cider.el | 21 +++++++++---- .../ROOT/pages/basics/up_and_running.adoc | 8 +++++ .../ROOT/pages/usage/code_evaluation.adoc | 6 ++++ test/cider-tests.el | 30 ++++++++++++++----- 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3fbe2d3..c35c5b0de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### New features - [#3692](https://github.com/clojure-emacs/cider/pull/3692): Add ability to switch view modes in the ispector (bound to `v`). +- [#3693](https://github.com/clojure-emacs/cider/pull/3693): Add `cider-enable-nrepl-jvmti-agent` defcustom to enable loading native nREPL JVMTI agent which restores thread stop ability on Java 21+. ### Changes diff --git a/cider.el b/cider.el index 67e83bd5e..0d46f5daa 100644 --- a/cider.el +++ b/cider.el @@ -173,7 +173,6 @@ then concatenated into the \"-M[your-aliases]:cider/nrepl\" form." :safe #'stringp :package-version '(cider . "1.1")) - (defcustom cider-clojure-cli-global-aliases nil "Global aliases to include when jacking in with the clojure CLI. @@ -186,7 +185,6 @@ then concatenated into the \"-M[your-aliases]:cider/nrepl\" form." :safe #'stringp :package-version '(cider . "1.14")) - (defcustom cider-shadow-cljs-command "npx shadow-cljs" "The command used to execute shadow-cljs. @@ -376,6 +374,14 @@ The repl dependendcies are most likely to be nREPL middlewares." :safe #'booleanp :version '(cider . "0.11.0")) +(defcustom cider-enable-nrepl-jvmti-agent nil + "When t, add `-Djdk.attach.allowAttachSelf' to the command line arguments. +This allows nREPL JVMTI agent to be loaded. It is needed for evaluation +interruption to properly work on Java 21 and above." + :type 'boolean + :safe #'booleanp + :version '(cider . "1.15.0")) + (defcustom cider-offer-to-open-cljs-app-in-browser t "When nil, do not offer to open ClojureScript apps in a browser on connect." :type 'boolean @@ -536,7 +542,7 @@ Throws an error if PROJECT-TYPE is unknown." "List of dependencies where elements are lists of artifact name and version.") (put 'cider-jack-in-dependencies 'risky-local-variable t) -(defcustom cider-injected-nrepl-version "1.1.2" +(defcustom cider-injected-nrepl-version "1.2.0-beta2" "The version of nREPL injected on jack-in. We inject the newest known version of nREPL just in case your version of Boot or Leiningen is bundling an older one." @@ -762,6 +768,8 @@ group:artifact:version notation and MIDDLEWARES are prepared as arguments to Clojurephant's ClojureNRepl task." (concat global-opts (unless (seq-empty-p global-opts) " ") + (when cider-enable-nrepl-jvmti-agent + "-Pjdk.attach.allowAttachSelf ") (cider--gradle-jack-in-property (append (cider--jack-in-required-dependencies) dependencies)) " " params @@ -813,7 +821,9 @@ removed, LEIN-PLUGINS, LEIN-MIDDLEWARES and finally PARAMS." (seq-map (lambda (middleware) (concat "update-in :middleware conj " middleware)) - lein-middlewares)) + lein-middlewares) + (when cider-enable-nrepl-jvmti-agent + `(,(concat "update-in :jvm-opts conj -Djdk.attach.allowAttachSelf")))) " -- ") " -- " (if (not cider-enrich-classpath) @@ -903,9 +913,10 @@ your aliases contain any mains, the cider/nrepl one will be the one used." (deps (format "{:deps {%s} :aliases {:cider/nrepl {:main-opts [%s]}}}" (string-join all-deps " ") main-opts)) (deps-quoted (cider--shell-quote-argument deps command))) - (format "%s-Sdeps %s -M%s:cider/nrepl%s" + (format "%s%s-Sdeps %s -M%s:cider/nrepl%s" ;; TODO: global-options are deprecated and should be removed in CIDER 2.0 (if global-options (format "%s " global-options) "") + (if cider-enable-nrepl-jvmti-agent "-J-Djdk.attach.allowAttachSelf " "") deps-quoted (cider--combined-aliases) (if params (format " %s" params) "")))) diff --git a/doc/modules/ROOT/pages/basics/up_and_running.adoc b/doc/modules/ROOT/pages/basics/up_and_running.adoc index baaa0d97b..66b74870d 100644 --- a/doc/modules/ROOT/pages/basics/up_and_running.adoc +++ b/doc/modules/ROOT/pages/basics/up_and_running.adoc @@ -204,6 +204,14 @@ You can further customize the command line CIDER uses for `cider-jack-in` by modifying the some options. Those differ a bit between the various tools, so we'll examine them tool by tool. +==== Enabling nREPL JVMTI agent + +Since version 1.2.0, nREPL ships together with a native JVMTI agent, so that the +eval interrupts properly work on Java 21 and later. To enable the agent, the +Java process should be launched with `-Djdk.attach.allowAttachSelf`. CIDER will +do it automatically during jack-in if `cider-enable-nrepl-jvmti-agent` is set to +`t`. + ==== Leiningen Options * `cider-lein-command` - the name of the Leiningen executable (`lein` by default) diff --git a/doc/modules/ROOT/pages/usage/code_evaluation.adoc b/doc/modules/ROOT/pages/usage/code_evaluation.adoc index dc3e625de..a2f21dc45 100644 --- a/doc/modules/ROOT/pages/usage/code_evaluation.adoc +++ b/doc/modules/ROOT/pages/usage/code_evaluation.adoc @@ -161,6 +161,12 @@ NOTE: CIDER internally increases the timeout to 30 seconds for the first sync ev == Configuration +=== Enable evaluation interrupts on Java 21 and newer + +The configuration variable `cider-enable-nrepl-jvmti-agent` has to be enabled +for interrupts to work properly on Java 21+. See +xref:basics/up_and_running.adoc#enabling-nrepl-jvmti-agent[JVMTI agent] section. + === Display Spinner During Evaluation Some evaluation operations take a while to complete, so CIDER will display a diff --git a/test/cider-tests.el b/test/cider-tests.el index 01fb01b60..b900a3999 100644 --- a/test/cider-tests.el +++ b/test/cider-tests.el @@ -147,7 +147,8 @@ (setq-local cider-injected-middleware-version "0.49.0") (setq-local cider-jack-in-nrepl-middlewares '("cider.nrepl/cider-middleware")) (setq-local cider-jack-in-dependencies-exclusions '()) - (setq-local cider-enrich-classpath t)) + (setq-local cider-enrich-classpath t) + (setq-local cider-enable-nrepl-jvmti-agent t)) (it "can inject dependencies in a lein project" (expect (cider-inject-jack-in-dependencies "" "repl :headless" 'lein) @@ -157,6 +158,7 @@ (shell-quote-argument "[cider/cider-nrepl \"0.49.0\"]") " -- update-in :plugins conj " (shell-quote-argument "[mx.cider/lein-enrich-classpath \"1.19.3\"]") + " -- update-in :jvm-opts conj -Djdk.attach.allowAttachSelf" " -- update-in :middleware conj cider.enrich-classpath.plugin-v2/middleware" " -- repl :headless"))) @@ -170,6 +172,7 @@ (shell-quote-argument "[cider/cider-nrepl \"0.49.0\"]") " -- update-in :plugins conj " (shell-quote-argument "[mx.cider/lein-enrich-classpath \"1.19.3\"]") + " -- update-in :jvm-opts conj -Djdk.attach.allowAttachSelf" " -- update-in :middleware conj cider.enrich-classpath.plugin-v2/middleware" " -- repl :headless"))) @@ -182,6 +185,7 @@ (shell-quote-argument "[cider/cider-nrepl \"0.49.0\"]") " -- update-in :plugins conj " (shell-quote-argument "[mx.cider/lein-enrich-classpath \"1.19.3\"]") + " -- update-in :jvm-opts conj -Djdk.attach.allowAttachSelf" " -- update-in :middleware conj cider.enrich-classpath.plugin-v2/middleware" " -- repl :headless"))) @@ -201,6 +205,7 @@ (it "can inject dependencies in a gradle project" (expect (cider-inject-jack-in-dependencies "--no-daemon" ":clojureRepl" 'gradle) :to-equal (concat "--no-daemon " + "-Pjdk.attach.allowAttachSelf " (shell-quote-argument "-Pdev.clojurephant.jack-in.nrepl=nrepl:nrepl:0.9.0,cider:cider-nrepl:0.49.0") " :clojureRepl " (shell-quote-argument "--middleware=cider.nrepl/cider-middleware"))))) @@ -221,6 +226,7 @@ (shell-quote-argument "[cider/cider-nrepl \"0.49.0\"]") " -- update-in :plugins conj " (shell-quote-argument "[mx.cider/lein-enrich-classpath \"1.19.3\"]") + " -- update-in :jvm-opts conj -Djdk.attach.allowAttachSelf" " -- update-in :middleware conj cider.enrich-classpath.plugin-v2/middleware" " -- repl :headless"))) @@ -256,6 +262,7 @@ (shell-quote-argument "[cider/cider-nrepl \"0.49.0\"]") " -- update-in :plugins conj " (shell-quote-argument "[mx.cider/lein-enrich-classpath \"1.19.3\"]") + " -- update-in :jvm-opts conj -Djdk.attach.allowAttachSelf" " -- update-in :middleware conj cider.enrich-classpath.plugin-v2/middleware" " -- repl :headless"))) (it "can concat in a boot project" @@ -272,6 +279,7 @@ (it "can concat in a gradle project" (expect (cider-inject-jack-in-dependencies "--no-daemon" ":clojureRepl" 'gradle) :to-equal (concat "--no-daemon " + "-Pjdk.attach.allowAttachSelf " (shell-quote-argument "-Pdev.clojurephant.jack-in.nrepl=nrepl:nrepl:0.9.0,cider:cider-nrepl:0.49.0") " :clojureRepl " (shell-quote-argument "--middleware=cider.nrepl/cider-middleware"))))) @@ -337,6 +345,7 @@ (shell-quote-argument "[cider/cider-nrepl \"0.49.0\"]") " -- update-in :plugins conj " (shell-quote-argument "[mx.cider/lein-enrich-classpath \"1.19.3\"]") + " -- update-in :jvm-opts conj -Djdk.attach.allowAttachSelf" " -- update-in :middleware conj cider.enrich-classpath.plugin-v2/middleware" " -- repl :headless")))) @@ -446,7 +455,7 @@ (it "uses main opts in an alias to prevent other mains from winning" (setq-local cider-jack-in-dependencies nil) (setq-local cider-jack-in-nrepl-middlewares '("cider.nrepl/cider-middleware")) - (let ((expected (string-join `("clojure -Sdeps " + (let ((expected (string-join `("clojure -J-Djdk.attach.allowAttachSelf -Sdeps " ,(shell-quote-argument "{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} cider/cider-nrepl {:mvn/version \"0.49.0\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") " -M:cider/nrepl") ""))) @@ -454,13 +463,14 @@ (setq-local cider-clojure-cli-command "clojure") (setq-local cider-inject-dependencies-at-jack-in t) (setq-local cider-clojure-cli-aliases nil) + (setq-local cider-enable-nrepl-jvmti-agent t) (spy-on 'cider-project-type :and-return-value 'clojure-cli) (spy-on 'cider-jack-in-resolve-command :and-return-value "clojure") (expect (plist-get (cider--update-jack-in-cmd nil) :jack-in-cmd) :to-equal expected))) (it "allows specifying custom aliases with `cider-clojure-cli-aliases`" - (let ((expected (string-join `("clojure -Sdeps " + (let ((expected (string-join `("clojure -J-Djdk.attach.allowAttachSelf -Sdeps " ,(shell-quote-argument "{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} cider/cider-nrepl {:mvn/version \"0.49.0\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") " -M:dev:test:cider/nrepl") ""))) @@ -469,6 +479,7 @@ (setq-local cider-allow-jack-in-without-project t) (setq-local cider-clojure-cli-command "clojure") (setq-local cider-inject-dependencies-at-jack-in t) + (setq-local cider-enable-nrepl-jvmti-agent t) (spy-on 'cider-project-type :and-return-value 'clojure-cli) (spy-on 'cider-jack-in-resolve-command :and-return-value "clojure") (expect (plist-get (cider--update-jack-in-cmd nil) :jack-in-cmd) @@ -477,7 +488,8 @@ (dolist (command '("clojure" "powershell")) (it (format "should remove duplicates, yielding the same result (for %S command invocation)" command) ;; repeat the same test for PowerShell too - (let ((expected (string-join `("-Sdeps " + (let ((expected (string-join `("-J-Djdk.attach.allowAttachSelf " + "-Sdeps " ,(cider--shell-quote-argument "{:deps {cider/cider-nrepl {:mvn/version \"0.49.0\"} nrepl/nrepl {:mvn/version \"0.9.0\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}" command) " -M:dev:test:cider/nrepl") @@ -487,7 +499,8 @@ command) :to-equal expected)))) (it "handles aliases correctly" - (let ((expected (string-join `("-Sdeps " + (let ((expected (string-join `("-J-Djdk.attach.allowAttachSelf " + "-Sdeps " ,(shell-quote-argument "{:deps {cider/cider-nrepl {:mvn/version \"0.49.0\"} nrepl/nrepl {:mvn/version \"0.9.0\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") " -M:test:cider/nrepl") "")) @@ -515,18 +528,18 @@ (expect (cider-clojure-cli-jack-in-dependencies nil nil deps) :to-equal expected))))) (it "allows for global options" - (let ((expected (string-join `("-J-Djdk.attach.allowAttachSelf -Sdeps " + (let ((expected (string-join `("-J-Xverify:none -J-Djdk.attach.allowAttachSelf -Sdeps " ,(shell-quote-argument "{:deps {cider/cider-nrepl {:mvn/version \"0.49.0\"} nrepl/nrepl {:mvn/version \"0.9.0\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") " -M:test:cider/nrepl") "")) (deps '(("nrepl/nrepl" "0.9.0")))) (let ((cider-clojure-cli-aliases ":test")) - (expect (cider-clojure-cli-jack-in-dependencies "-J-Djdk.attach.allowAttachSelf" nil deps) + (expect (cider-clojure-cli-jack-in-dependencies "-J-Xverify:none" nil deps) :to-equal expected)))) (it "allows to specify git coordinate as cider-jack-in-dependency" (setq-local cider-jack-in-dependencies '(("org.clojure/tools.deps" (("git/sha" . "6ae2b6f71773de7549d7f22759e8b09fec27f0d9") ("git/url" . "https://github.com/clojure/tools.deps/"))))) - (let ((expected (string-join `("clojure -Sdeps " + (let ((expected (string-join `("clojure -J-Djdk.attach.allowAttachSelf -Sdeps " ,(shell-quote-argument "{:deps {nrepl/nrepl {:mvn/version \"0.9.0\"} cider/cider-nrepl {:mvn/version \"0.49.0\"} org.clojure/tools.deps { :git/sha \"6ae2b6f71773de7549d7f22759e8b09fec27f0d9\" :git/url \"https://github.com/clojure/tools.deps/\" }} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}") " -M:cider/nrepl") ""))) @@ -534,6 +547,7 @@ (setq-local cider-clojure-cli-command "clojure") (setq-local cider-inject-dependencies-at-jack-in t) (setq-local cider-clojure-cli-aliases nil) + (setq-local cider-enable-nrepl-jvmti-agent t) (spy-on 'cider-project-type :and-return-value 'clojure-cli) (spy-on 'cider-jack-in-resolve-command :and-return-value "clojure") (expect (plist-get (cider--update-jack-in-cmd nil) :jack-in-cmd)