branch: elpa/cider
commit 49b15275d771db3a627dfcb37ec10df45f1adbf0
Author: Oleksandr Yakushev <a...@bytopia.org>
Commit: Bozhidar Batsov <bozhi...@batsov.dev>

    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 ++++++++++++----
 doc/modules/ROOT/pages/basics/up_and_running.adoc |  8 ++++++
 doc/modules/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 ce3fbe2d36..c35c5b0de8 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 67e83bd5e8..0d46f5daa5 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 baaa0d97b7..66b74870d4 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 dc3e625de7..a2f21dc45c 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 01fb01b601..b900a39995 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)

Reply via email to