From 82d8bb7ffb095dbe0b658ac168a213e3e3c5ad25 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 7 Nov 2026 09:04:23 +0200 Subject: [PATCH] Point the reload workarounds at the cider-nrepl fixes for them cider-nrepl#1077 relinks aliases and refers after its reloads, and cider-nrepl#1078 puts the session that asked for one back in its namespace, so neorepl's relink-code and reenter-code can go once a release with them is the oldest one supported. The gotcha also said tools.namespace's refresh puts the session back, which its REPL function does, but cider-nrepl's refresh op doesn't. --- PLAN.md | 4 ++-- src/neorepl/features.clj | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/PLAN.md b/PLAN.md index a39fc33..7a37d24 100644 --- a/PLAN.md +++ b/PLAN.md @@ -108,7 +108,7 @@ Runtime deps: `org.clojure/clojure`, `nrepl/nrepl`, `org.jline/jline` 4.x. That' - Gotcha: a subscription switches orchard from printing to sending events for the whole server, so while one's open, `neorepl eval` and every other client get no printed traces. The REPL subscribes only once something's traced, and unsubscribes when it ends. A client that goes away without unsubscribing leaves the server sending events to nobody until a traced call finds the connection gone, which drops the subscription since cider-nrepl 0.62.2, losing that one call's trace. - Reloading what changed (2026-11-03, cider-nrepl 0.64.0, with clj-reload 1.0.0). `cider.clj-reload/reload` runs in the session it names, after what's running there, and answers with `:progress` lines, "Reloading N namespaces..." and then "Reloaded N namespaces in M ms" or " failed to load NS" with the error's first line, then `ok`, or `error` with the analyzed causes as `:error` and what `repl-caught` prints on `:err`, and `done`. It reloads the namespaces whose files changed and the ones that require them, in dependency order, leaves alone the ones nothing loaded, and keeps what `defonce` defined, through a failed reload too, which keeps the namespace that failed for the next one. In a ClojureScript session it answers `done` and `clojure-only`, with a word on `:err`. - Gotcha: cider-nrepl loads a middleware's namespace the first time its op comes, and clj-reload notes the files' times when it loads, so on a server that's never reloaded, the first reload only starts noting what changes, and takes about half a second. A file changed before then doesn't get reloaded. neorepl sends that first reload in the background as soon as a server it starts with cider-nrepl is up, so `eval --reload` there sees everything since. On other servers, the first `--reload` or `,autoreload` is where it starts. cider-nrepl starting clj-reload with the server would do away with it. -- Gotcha: clj-reload makes the namespaces it reloads anew, and a session in one goes on in the old one, where a function just added to the file doesn't resolve. Unlike tools.namespace's `refresh`, it doesn't put the session back, so neorepl does, after a reload that reloaded something, evaluating `in-ns` there in a way that leaves `*1`, `*2` and `*3` as they were. babashka resolves names through the namespace's name, so it doesn't go stale there. +- Gotcha: clj-reload makes the namespaces it reloads anew, and a session in one goes on in the old one, where a function just added to the file doesn't resolve. Unlike `clojure.tools.namespace.repl/refresh`, it doesn't put the session back, and neither does cider-nrepl's `refresh` op, which calls tools.namespace's `track-reload` itself, so neorepl does, after a reload that reloaded something, evaluating `in-ns` there in a way that leaves `*1`, `*2` and `*3` as they were. babashka resolves names through the namespace's name, so it doesn't go stale there. [cider-nrepl#1078](https://github.com/clojure-emacs/cider-nrepl/pull/1078) does it in cider-nrepl after both reloads, for the session that asks for one, and once a release with it is the oldest one supported, neorepl's can go. - Gotcha: the same goes for the aliases and refers of the namespaces a reload leaves alone, like the REPL's own, with clj-reload and with cider-nrepl's `refresh`, whose tools.namespace also makes namespaces anew with `remove-ns` (2026-11-06, cider-nrepl 0.64.0 and clj-reload 1.0.0, whose README says so). They stay on the old namespace and its vars, so a function added to the file since doesn't resolve through the alias, and requiring the namespace with the same alias again fails with "Alias o already exists". Calling a function that was there before works, since compiled code finds vars by their names. So after a reload that reloaded something, neorepl has every alias and refer that's on a namespace that's gone point at the one by that name now, and its var by that name, a few milliseconds with cider-nrepl's namespaces loaded. What has nothing new to point at stays as it was, since an `:as-alias` keeps working for keywords, and another client's reload can be halfway through. It goes by the var's own namespace and name, since potemkin's `import-vars` copies another var's `:ns` and `:name` into its metadata. Only Clojure servers have these reloads, so it's Clojure only. [cider-nrepl#1077](https://github.com/clojure-emacs/cider-nrepl/pull/1077) does it in cider-nrepl after both reloads, for every client, and once a release with it is the oldest one supported, neorepl's can go. - Output from other threads (2026-11-03, nREPL 1.8.0, with piggieback 0.8.0 too, bb 1.13.227 and 1.13.229, and shadow-cljs 3.5.7, which brings nREPL 1.3.1 and can't). `forward-system-output` swaps in `System/out` and `System/err`, and root bindings of `*out*` and `*err*`, which threads no session started print to, that copy what's printed to the session that asked, as `:out` or `:err` with `:source "system"` on that request's id, after its `done`. An evaluation prints to its session's `*out*` as before, unless it writes to `System/out` itself. nREPL keeps one forwarding per session, so asking again replaces it. cider-nrepl 0.64.0's `cider/out-subscribe`, the way before it, sends nothing to a session that hasn't evaluated anything, and its `cider/out-unsubscribe` leaves the session subscribed and fails to send its answer, which carries the whole request ([cider-nrepl#1074](https://github.com/clojure-emacs/cider-nrepl/issues/1074)), so neorepl leaves it be. What comes forwarded is all the server prints, its own noise included, like the `SocketException` traces of [babashka#2227](https://github.com/babashka/babashka/issues/2227). - Gotcha: nREPL never drops a session's forwarding, not even once the session's closed ([nrepl#513](https://github.com/nrepl/nrepl/issues/513)), so every later write goes to a connection that's gone, which fails without a word, and the request it keeps holds on to the session, with its `*1` and `*e`. So the REPL asks in a session of its own, which never evaluates anything, and closes it when it ends. And a client in the server's process gets sent what it prints, and prints it again, forever, so `forward-system-output` doesn't ask a server in neorepl's process, nor one neorepl started, whose output the REPL shows already. @@ -395,7 +395,7 @@ Not covered yet: Linux (Docker wasn't running) and Windows. Neither should surpr 1. CI against real servers, see 0.5. So far the tests run on the JVM and bb against an in-process nREPL, with fakes for the cider-nrepl ops, and the real servers get checked by hand and with proof. 2. babashka. Filed: `test-var-query` going quiet for a test that throws outside an assertion, [babashka#2201](https://github.com/babashka/babashka/issues/2201), fixed after 1.13.226, and, a few at a time to keep from flooding the tracker, on 2026-10-21 and 2026-10-22: `test-var-query` by var for a namespace that isn't loaded killing the session ([#2206](https://github.com/babashka/babashka/issues/2206)), `:project?` and `:load-project-ns?` being ignored ([#2207](https://github.com/babashka/babashka/issues/2207)), test results carrying the file of the session's last `load-file` ([#2208](https://github.com/babashka/babashka/issues/2208)), `info` without `:line` or `:column`, resolving built-in files against the working directory ([#2209](https://github.com/babashka/babashka/issues/2209)), the `SocketException` from `nREPL-track-state` after every client that disconnects ([#2210](https://github.com/babashka/babashka/issues/2210)), `inspect-last-exception` ([#2211](https://github.com/babashka/babashka/issues/2211)), `org.jline.utils.Status` (mid-buffer eldoc) and `org.jline.reader.CompletingParsedLine` (for reify) ([#2218](https://github.com/babashka/babashka/issues/2218)), `jdk.net.ExtendedSocketOptions`, for `TCP_QUICKACK` ([#2219](https://github.com/babashka/babashka/issues/2219)), and `source-fn`, which resolves bb's own dependencies the first time it's asked for a built-in, cloning git deps, which is what timed out on fresh Windows runners, and keeps `nil` for good when that fails, which is what failed `source when` for the whole run on 2026-10-18 ([#2220](https://github.com/babashka/babashka/issues/2220)). All of them are fixed on bb's master, not released yet. The 1.13.227-SNAPSHOT dev build fixes each one, on macOS and on Linux in Docker, and passes neorepl's tests; with `TCP_QUICKACK` there, `eval -f` of 100 forms on Linux goes from 4.5s to 154ms. Its `inspect-last-exception`, like cider-nrepl's, inspects what `analyze-last-stacktrace` analyzed, so the stacktrace has to be asked for first. Still open, from 2026-10-22: a session whose client disconnects during an evaluation never answers again, since nREPL 1.7's session thread dies writing the reply, which nREPL 1.8 fixed ([#2227](https://github.com/babashka/babashka/issues/2227)). It's also the `nREPL-session` SocketException in the output of neorepl's tests on bb. bb's server has been a vendored nREPL 1.7 since 1.13.221, so the interrupted evaluation's exception after `done` is nREPL's, [nrepl#506](https://github.com/nrepl/nrepl/issues/506), and so are its sockets without `TCP_NODELAY`, [nrepl#507](https://github.com/nrepl/nrepl/issues/507). On 2026-10-31, what tests print going out under the session's previous message, as it did in cider-nrepl before 0.64.0 ([#2265](https://github.com/babashka/babashka/issues/2265)). On 2026-11-01, `info` knowing nothing about namespaces ([#2270](https://github.com/babashka/babashka/issues/2270)), its `lookup` being the vendored nREPL's, which nrepl#511 covers. On 2026-11-04, `print-method` and `clojure.pprint/write` not seeing `binding`s of `*print-length*` and `*print-level*`, which nREPL's printers count on ([#2274](https://github.com/babashka/babashka/issues/2274)). Still to talk over with borkdude: how `bb repl --connect` and neorepl should relate long term. 3. nREPL. Filed: `need-input` going to the connection that created the session ([nrepl#183](https://github.com/nrepl/nrepl/issues/183)), interrupting an evaluation dropping the ones queued behind it ([nrepl#503](https://github.com/nrepl/nrepl/issues/503)), an interrupt not reaching an evaluation that hasn't started, queued or just sent ([nrepl#504](https://github.com/nrepl/nrepl/issues/504)), `*file*` staying bound after an eval with a `:file` ([nrepl#505](https://github.com/nrepl/nrepl/issues/505)), and an interrupted evaluation's InterruptedException coming after its `done` ([nrepl#506](https://github.com/nrepl/nrepl/issues/506)). [#498](https://github.com/nrepl/nrepl/pull/498), the interrupted evaluation's last response going to its own connection, is merged. The 1.6 warnings about middleware a descriptor names and the stack lacks are [nrepl#424](https://github.com/nrepl/nrepl/issues/424): with piggieback on the classpath, cider-nrepl's descriptors bring eleven of them, and shadow-cljs's own one more, since it expects piggieback's middleware without listing it; one line per missing middleware, and none for `:expects`, would quiet it. On 2026-10-22: the server's sockets going without `TCP_NODELAY`, so on Linux an eval's `done` waits up to 40ms behind its value ([nrepl#507](https://github.com/nrepl/nrepl/issues/507)), which macOS doesn't show. The fix is [#508](https://github.com/nrepl/nrepl/pull/508), which takes 100 evals from nREPL's own client from 4.4s to 90ms on Linux. Until servers have it, neorepl sets `TCP_QUICKACK` before each read, on the JVM and on bb, which has `ExtendedSocketOptions` from 1.13.227. On 2026-11-01, `lookup` finding namespaces, by their name or an alias, [#511](https://github.com/nrepl/nrepl/pull/511), which also leaves `:file` out where there's none, rather than sending an empty list, as it does for a var made with `intern`. On 2026-11-03, closing a session not stopping its `forward-system-output` ([nrepl#513](https://github.com/nrepl/nrepl/issues/513)). -4. cider-nrepl and orchard. Filed: ClojureScript tests not running on shadow-cljs ([cider-nrepl#1054](https://github.com/clojure-emacs/cider-nrepl/issues/1054)), and on 2026-10-21 `test-var-query` never answering for a test whose namespace isn't loaded ([#1057](https://github.com/clojure-emacs/cider-nrepl/issues/1057)), running every test when the tests it names aren't there ([#1058](https://github.com/clojure-emacs/cider-nrepl/issues/1058)), test output going out under the session's previous message ([#1059](https://github.com/clojure-emacs/cider-nrepl/issues/1059)), ClojureScript test runs leaving the report in `*1` ([#1060](https://github.com/clojure-emacs/cider-nrepl/issues/1060)) and `cider/trace-subscribe` answering `done` before its events ([#1061](https://github.com/clojure-emacs/cider-nrepl/issues/1061)). In orchard: the namespace query's NullPointerException once a project namespace fails to load ([orchard#434](https://github.com/clojure-emacs/orchard/issues/434), #70 again), which is what hangs `test-var-query` with `:load-project-ns?`, a traced call that throws getting no exit line or event ([orchard#435](https://github.com/clojure-emacs/orchard/issues/435)), and tracing what orchard's tracing calls, like `clojure.core/map`, taking the server down ([orchard#436](https://github.com/clojure-emacs/orchard/issues/436)). On 2026-10-26, the spaces orchard puts between everything in a Javadoc's HTML and its unescaped `{@code}`, with the fix ([orchard#438](https://github.com/clojure-emacs/orchard/pull/438)). A subscription outliving its connection was fixed in 0.62.2. orchard 0.48.0 and cider-nrepl 0.64.0 fix #1054, #1057, #1058, #1059, orchard#434 and orchard#435. orchard#436 is only half fixed: tracing a function orchard's tracing calls no longer takes the server down, but orchard still traces it, since refusing takes a status in cider-nrepl that CIDER can show. Still open: #1060, and #1061, which waits until most neorepl users have a REPL that takes its subscription without waiting for `done`, from 0.5.0 on. On 2026-11-03, `cider/out-unsubscribe` not unsubscribing and failing to send its answer ([#1074](https://github.com/clojure-emacs/cider-nrepl/issues/1074)), `cider/profile-summary` failing until a profiled function gets called ([#1072](https://github.com/clojure-emacs/cider-nrepl/issues/1072)), no ops to list what's profiled or stop all of it ([#1073](https://github.com/clojure-emacs/cider-nrepl/issues/1073)), a value that can't be printed ending a tap subscription ([#1075](https://github.com/clojure-emacs/cider-nrepl/issues/1075)), tapped values kept once per subscription and after the last one ([#1076](https://github.com/clojure-emacs/cider-nrepl/issues/1076)), and in orchard, profiling maps and multimethods ([orchard#445](https://github.com/clojure-emacs/orchard/issues/445)). +4. cider-nrepl and orchard. Filed: ClojureScript tests not running on shadow-cljs ([cider-nrepl#1054](https://github.com/clojure-emacs/cider-nrepl/issues/1054)), and on 2026-10-21 `test-var-query` never answering for a test whose namespace isn't loaded ([#1057](https://github.com/clojure-emacs/cider-nrepl/issues/1057)), running every test when the tests it names aren't there ([#1058](https://github.com/clojure-emacs/cider-nrepl/issues/1058)), test output going out under the session's previous message ([#1059](https://github.com/clojure-emacs/cider-nrepl/issues/1059)), ClojureScript test runs leaving the report in `*1` ([#1060](https://github.com/clojure-emacs/cider-nrepl/issues/1060)) and `cider/trace-subscribe` answering `done` before its events ([#1061](https://github.com/clojure-emacs/cider-nrepl/issues/1061)). In orchard: the namespace query's NullPointerException once a project namespace fails to load ([orchard#434](https://github.com/clojure-emacs/orchard/issues/434), #70 again), which is what hangs `test-var-query` with `:load-project-ns?`, a traced call that throws getting no exit line or event ([orchard#435](https://github.com/clojure-emacs/orchard/issues/435)), and tracing what orchard's tracing calls, like `clojure.core/map`, taking the server down ([orchard#436](https://github.com/clojure-emacs/orchard/issues/436)). On 2026-10-26, the spaces orchard puts between everything in a Javadoc's HTML and its unescaped `{@code}`, with the fix ([orchard#438](https://github.com/clojure-emacs/orchard/pull/438)). A subscription outliving its connection was fixed in 0.62.2. orchard 0.48.0 and cider-nrepl 0.64.0 fix #1054, #1057, #1058, #1059, orchard#434 and orchard#435. orchard#436 is only half fixed: tracing a function orchard's tracing calls no longer takes the server down, but orchard still traces it, since refusing takes a status in cider-nrepl that CIDER can show. Still open: #1060, and #1061, which waits until most neorepl users have a REPL that takes its subscription without waiting for `done`, from 0.5.0 on. On 2026-11-03, `cider/out-unsubscribe` not unsubscribing and failing to send its answer ([#1074](https://github.com/clojure-emacs/cider-nrepl/issues/1074)), `cider/profile-summary` failing until a profiled function gets called ([#1072](https://github.com/clojure-emacs/cider-nrepl/issues/1072)), no ops to list what's profiled or stop all of it ([#1073](https://github.com/clojure-emacs/cider-nrepl/issues/1073)), a value that can't be printed ending a tap subscription ([#1075](https://github.com/clojure-emacs/cider-nrepl/issues/1075)), tapped values kept once per subscription and after the last one ([#1076](https://github.com/clojure-emacs/cider-nrepl/issues/1076)), and in orchard, profiling maps and multimethods ([orchard#445](https://github.com/clojure-emacs/orchard/issues/445)). After a reload, the aliases and refers left on the namespaces it removed are fixed on master by [#1077](https://github.com/clojure-emacs/cider-nrepl/pull/1077) (2026-11-06), and the session left in one by [#1078](https://github.com/clojure-emacs/cider-nrepl/pull/1078), open since 2026-11-07, neither released yet. 5. piggieback: evaluating every form of an `eval` message, the way nREPL's own eval does, and loading ClojureScript on the first `cljs-repl` rather than at startup, both open as [#162](https://github.com/nrepl/piggieback/pull/162) and [#161](https://github.com/nrepl/piggieback/pull/161), and `describe` reporting `cljs.user` after an `ns` switch ([piggieback#163](https://github.com/nrepl/piggieback/issues/163)). Its README says ClojureScript evaluations can't be interrupted, which for Node isn't the whole story, see the gotcha. In ClojureScript: interrupting the Node REPL, and two Node REPLs sharing a result queue, see the gotcha, to raise on ask.clojure.org. 6. JLine: `TAIL_TIP` hiding the incremental search's prompt ([jline3#2330](https://github.com/jline/jline3/issues/2330)), `LineDisciplineTerminal` echoing `^C` with ECHO off ([#2331](https://github.com/jline/jline3/issues/2331)) and `setTailTip(null)` throwing ([#2332](https://github.com/jline/jline3/issues/2332)). A tail tip that doesn't start with `[` loses its first word while a word without a `-` is typed, which is by design, for TailTipWidgets' argument lists; eldoc's tips for special forms start with `(`, so they lose it, and starting them with a space would keep them whole. Since 2026-11-01 eldoc goes under the input, and neorepl has no tail tips. 7. nbb. Filed on 2026-11-02: code that throws getting no `eval-error` status ([nbb#425](https://github.com/babashka/nbb/issues/425)), fixed the same day, after 1.6.214, an eval's `:ns` losing to the namespace the last eval left ([#426](https://github.com/babashka/nbb/issues/426)), `in-ns` not switching ([#427](https://github.com/babashka/nbb/issues/427)), sessions sharing the namespace, `*1` and `*e` ([#429](https://github.com/babashka/nbb/issues/429)), `load-file` taking the file's contents for its `:file` ([#430](https://github.com/babashka/nbb/issues/430)), and `info` and `eldoc` answering what they don't find with empty lists and no `no-info` or `no-eldoc` ([#431](https://github.com/babashka/nbb/issues/431)). On 2026-11-04, nREPL's own printers and a session's `*print-length*` being ignored ([#436](https://github.com/babashka/nbb/issues/436)). diff --git a/src/neorepl/features.clj b/src/neorepl/features.clj index 7232b90..5459a6a 100644 --- a/src/neorepl/features.clj +++ b/src/neorepl/features.clj @@ -1772,7 +1772,9 @@ "Puts the session back in its namespace, by its name, which a reload can make anew, leaving the session in the old one. The REPL then sets *3 to *2, *2 to *1 and *1 to the value, so the code sets them a step back and - comes to *1, which leaves all three as they were." + comes to *1, which leaves all three as they were. cider-nrepl puts the + session that asked for the reload back itself since cider-nrepl#1078, so + this can go once that's in the oldest one supported." "(clojure.core/let [v1 clojure.core/*1 v2 clojure.core/*2 v3 clojure.core/*3] (clojure.core/in-ns (clojure.core/ns-name clojure.core/*ns*)) (set! clojure.core/*2 v3)