I've been noticing that clojure-lsp is running out of channel put space when I run tests in a REPL. (error and stack trace in the thread) This happens when I connect to clojure-lsp's REPL from within Calva via nrepl and execute a bunch of tests. It seems to only happen when I run multiple deftests in the REPL via the command line (eg: (cond->if-test)). I haven't noticed it when I run a test case individually (via execute (via evaluate current form in Calva, where I'm evaluating a "testing" form). Is this something I'm doing wrong?
I found a reproducer (for me!) and entered https://github.com/clojure-lsp/clojure-lsp/issues/2172
It seems to be related to repeatedly calling h/load-code .
I also noticed that, at least for Fedora 42, clojure-lsp --verbose hangs on main.clj:197:
(defn ^:private handle-action!
[action options]
(if (= "listen" action)
(let [finished @(server/run-lsp-io-server! (:trace-level options) (:log-path options))]
{:result-code (if (= :done finished) 0 1)})
(try
...
The stack indicates that it's stuck waiting:
"main" #3 [453185] prio=5 os_prio=0 cpu=2743.99ms elapsed=10.01s tid=0x00007f395002b9b0 nid=453185 waiting on condition [0x00007f39547fb000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@25/Native Method)
- parking to wait for <0x0000000746a1a1f8> (a java.util.concurrent.CountDownLatch$Sync)
at java.util.concurrent.locks.LockSupport.park(java.base@25/LockSupport.java:223)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@25/AbstractQueuedSynchronizer.java:790)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(java.base@25/AbstractQueuedSynchronizer.java:1139)
at java.util.concurrent.CountDownLatch.await(java.base@25/CountDownLatch.java:230)
at clojure.core$promise$reify__8621.deref(core.clj:7257)
at clojure.core$deref.invokeStatic(core.clj:2337)
at clojure.core$deref.invoke(core.clj:2323)
at clojure_lsp.main$handle_action_BANG_.invokeStatic(main.clj:197)
at clojure_lsp.main$handle_action_BANG_.invoke(main.clj:194)
at clojure_lsp.main$run_BANG_.invokeStatic(main.clj:224)
at clojure_lsp.main$run_BANG_.doInvoke(main.clj:217)
at clojure.lang.RestFn.applyTo(RestFn.java:140)
at clojure.core$apply.invokeStatic(core.clj:667)
at clojure.core$apply.invoke(core.clj:662)
at clojure_lsp.main$main.invokeStatic(main.clj:227)
at clojure_lsp.main$main.doInvoke(main.clj:226)
at clojure.lang.RestFn.applyTo(RestFn.java:140)
at clojure.core$apply.invokeStatic(core.clj:667)
at clojure.core$apply.invoke(core.clj:662)
at clojure_lsp.main$_main.invokeStatic(main.clj:252)
at clojure_lsp.main$_main.doInvoke(main.clj:250)
at clojure.lang.RestFn.applyTo(RestFn.java:140)
at clojure_lsp.main.main(Unknown Source)thanks will take a look later on the issue about that one, I never saw that behavior
Here's the repl with the stack trace:
clj꞉clojure-lsp.refactor.transform-test꞉>
(cond->if-test);
; ERROR in () (channels.clj:157)
; two cond expressions that are simple
; expected: (= [(h/code "(if is-true" " :val-is-true" " :val-is-false)")] (as-strings (do-cond->if (str "(let [is-true true]\n" " |(cond\n" " is-true\n" " :val-is-true\n\n" " :else\n" " :val-is-false))"))))
; actual: java.lang.AssertionError: Assert failed: No more than 1024 pending puts are allowed on a single channel. Consider using a windowed buffer.
; (< (.size puts) impl/MAX-QUEUE-SIZE)
; at clojure.core.async.impl.channels.ManyToManyChannel.put_BANG_ (channels.clj:157)
; clojure.core.async$put_BANG_.invokeStatic (async.clj:201)
; clojure.core.async$put_BANG_.invoke (async.clj:189)
; clojure_lsp.feature.diagnostics$publish_diagnostic_BANG__STAR_.invokeStatic (diagnostics.clj:132)
; clojure_lsp.feature.diagnostics$publish_diagnostic_BANG__STAR_.invoke (diagnostics.clj:131)
; clojure_lsp.feature.diagnostics$publish_diagnostics_BANG_.invokeStatic (diagnostics.clj:146)
; clojure_lsp.feature.diagnostics$publish_diagnostics_BANG_.invoke (diagnostics.clj:145)
; clojure_lsp.feature.file_management$did_open.invokeStatic (file_management.clj:52)
; clojure_lsp.feature.file_management$did_open.invoke (file_management.clj:40)
; clojure_lsp.handlers$did_open.invokeStatic (handlers.clj:229)
; clojure_lsp.handlers$did_open.invoke (handlers.clj:224)
; clojure_lsp.test_helper.internal$load_code.invokeStatic (internal.clj:207)
; clojure_lsp.test_helper.internal$load_code.invoke (internal.clj:203)
; clojure_lsp.test_helper.internal$load_code_and_locs.invokeStatic (internal.clj:224)
; clojure_lsp.test_helper.internal$load_code_and_locs.invoke (internal.clj:219)
; clojure_lsp.test_helper.internal$load_code_and_locs.invokeStatic (internal.clj:221)
; clojure_lsp.test_helper.internal$load_code_and_locs.invoke (internal.clj:219)
; clojure_lsp.test_helper.internal$load_code_into_zloc_and_position.invokeStatic (internal.clj:240)
; clojure_lsp.test_helper.internal$load_code_into_zloc_and_position.invoke (internal.clj:234)
; clojure_lsp.test_helper.internal$load_code_and_zloc.invokeStatic (internal.clj:252)
; clojure_lsp.test_helper.internal$load_code_and_zloc.invoke (internal.clj:247)
; clojure_lsp.refactor.transform_test$do_cond__GT_if.invokeStatic (transform_test.clj:1670)
; clojure_lsp.refactor.transform_test$do_cond__GT_if.invoke (transform_test.clj:1668)
; clojure_lsp.refactor.transform_test$eval2663074.invokeStatic (NO_SOURCE_FILE:1691)
; clojure_lsp.refactor.transform_test$eval2663074.invoke (NO_SOURCE_FILE:1685)
; clojure.lang.Compiler.eval (Compiler.java:7700)
; nrepl.middleware.interruptible_eval$evaluator$run__1447$fn__1458.invoke (interruptible_eval.clj:106)
; nrepl.middleware.interruptible_eval$evaluator$run__1447.invoke (interruptible_eval.clj:101)
; nrepl.middleware.session$session_exec$session_loop__1526.invoke (session.clj:229)
; nrepl.SessionThread.run (SessionThread.java:21)
nil
The config info from Calva is:
Calva is utilizing cider-nrepl and clojure-lsp to create this VS Code experience.
Effective nREPL dependency versions:
nrepl: 1.5.1 (Calva defaults)
cider-nrepl: 0.58.0 (Calva defaults)
cider/piggieback: 0.6.1 (Calva defaults)
Latest available nREPL dependency versions found on Clojars:
nrepl: 1.5.1
cider-nrepl: 0.58.0
cider/piggieback: 0.6.1
clojure-lsp path configured: ./clojure-lsp
And this is on Linux, if that matters.
My clojure-lsp is based on a05196a5662e0794aff53981986b57a06b3dd5c5 (I think the latest master), but I did built it myself with extra refactorings that I'm adding.Once I get this error, I need to restart clojure-lsp. No big deal, but I'm curious if anyone has hints about the why.
yeah the JVM version sometimes gives that, it's something certainly to be improved in clojure-lsp async channels but TBH I don't know the best way to fix it
OK, I mostly wanted to be sure it wasn't something I did!
yeah it's not. it's related to the diagnostics chan
I would enter a bug, but I don't feel confident enough to explain it. 🙂
and it doesn't happen only in tests, but in JVM version of clojure-lsp some times when project has lots of diagnostics to publish
would be nice to find a consistent repro
a start
Ah. I can probably help with that
I'll see if I can make it reproducible
thank you!
The issue is using put! without a callback and using onto-chan! without blocking/parking on the channel it returns. This breaks backpressure allowing publishers to outpace consumers. That may or may not be what the discussion on https://github.com/clojure-lsp/clojure-lsp/pull/1196 is trying to get at, but the code merged for it definitely doesn't fix it.
The fix is either to use >!! to actually block (blocking is back pressure) or may require a larger rearchitecting depending on the context in which those functions are called, which I haven't looked at
yeah, that makes sense and looks like a good try, thanks @hiredman
@john.t.richardson.dev feel free to create a issue if you manage to create a repro so we can try that suggestion