Fork me on GitHub
#cursive
<
2021-09-19
>
devurandom15:09:50

I have an odd issue where Cursive's nREPL client seems to show things that REPL-y does not: https://clojurians.slack.com/archives/C17JYSA3H/p1632066344001900 Can someone help or maybe has some idea what could have caused this?

cfleming21:09:02

That looks to me like the REPL server doesn’t support some basic Clojure functionality that Cursive requires. Any idea what the server is or how it works?

cfleming21:09:56

Actually, it’s possible that what’s happening is that the results of Cursive’s initialisation are being printed there, where they wouldn’t normally be. Does the REPL seem to work ok apart from that?

cfleming21:09:55

It’s possible that the server’s responses are not quite what the standard nREPL’s ones would be, and that’s causing Cursive to print the results of some internal ops.

devurandom22:09:57

The server should be a standard nREPL 0.8.3: https://github.com/lambdaisland/witchcraft-plugin/blob/main/src/lambdaisland/witchcraft/plugin.clj#L66-L75 https://github.com/lambdaisland/witchcraft-plugin/blob/main/deps.edn#L21-L24 https://github.com/lambdaisland/witchcraft-plugin/blob/main/resources/witchcraft_plugin/default_config.edn.tmpl#L3-L10 But it uses a middleware that is supposed to execute each command in a different thread: https://github.com/lambdaisland/witchcraft/blob/main/src/lambdaisland/witchcraft/nrepl/task_eval.clj https://github.com/lambdaisland/witchcraft/blob/main/src/lambdaisland/witchcraft.clj#L523-L526 If I disable this middleware, the mysterious output is gone. Any idea what might be wrong with that code to modify the responses? Otherwise I'll continue poking it and read more about how nREPL works internally...

devurandom22:09:50

Something I am thinking about now, but which I will have to do more research on: Are nREPL commands supposed to be synchronous or can they be asynchronous? And does Cursive treat the nREPL connection as something stateful, e.g. switching "echo" off and on? That will be the next thing I will dig into...

cfleming22:09:11

nREPL commands are asynchronous. You send a message, and get one or more replies back. Unfortunately I know next to nothing about Minecraft, so I’m not sure why it would do that - looks like there’s a standard thread commands need to be executed on, like the Swing EDT or something?

devurandom22:09:37

Apart from the comment in that config EDN file, I do not know anything about this, either: > Run evaluated expressions inside the game loop, so you don't have to schedule them explicitly But yes, I assume game code needs to be executed from the game thread, maybe because the thing is not entirely thread safe... 🤷

cfleming22:09:00

I’m not really sure what that is doing. What I suspect is that either the message or session IDs are not coming back correctly. The way this works is that an ID is set on the request, and that should be provided on the response. Cursive maintains a map of response handlers for outstanding requests indexed by ID, and if it can’t find the correct handler for a response it will just use a default one. I suspect that’s what’s happening. Give me one sec…

cfleming22:09:12

Drat, I thought I had debug logging to see the message contents, but I don’t…

cfleming22:09:05

You could create a middleware to do that, perhaps…

cfleming22:09:21

Looks like this has been discussed here: https://github.com/nrepl/nrepl/issues/85

devurandom22:09:51

Thanks, that intro to nREPL helped a lot.

cfleming22:09:51

No worries, let me know if you have more questions.

plexus05:09:05

Thanks for the help @U0567Q30W, upon inspection it seems we were sending a :done to the eval op before sending the :value response. Would that explain this behavior? I've put out a fix.

plexus05:09:40

Before:

C ---> 11 eval {:nrepl.middleware.print/buffer-size 4096, :ns "user", :file "*cider-repl clj-projects/cauldron:localhost:25554(clj)*", :nrepl.middleware.print/quota 1048576, :nrepl.middleware.print/print "cider.nrepl.pprint/pprint", :column 7, :line 13, :code "(+ 1 1)", :nrepl.middleware.print/stream? "1", :nrepl.middleware.print/options {:right-margin 80}}
C <=== 11 #{:done} {}
C <--- 11 #{} {:value "2"}
C <--- 11 #{} {:ns "user"}
After
C ---> 11 eval {:nrepl.middleware.print/buffer-size 4096, :ns "user", :file "*cider-repl clj-projects/cauldron:localhost:25554(clj)*", :nrepl.middleware.print/quota 1048576, :nrepl.middleware.print/print "cider.nrepl.pprint/pprint", :column 7, :line 13, :code "(+ 1 1)", :nrepl.middleware.print/stream? "1", :nrepl.middleware.print/options {:right-margin 80}}
C <--- 11 #{} {:value "2"}
C <--- 11 #{} {:ns "user"}
C <=== 11 #{:done} {}

cfleming11:09:41

Yes, that would explain it in Cursive’s case. Cursive keeps a map of handlers for outstanding requests around, indexed by message ID. When it receives a :done message, the handler is removed from the map of outstanding requests, and any further responses for that ID will go to a generic handler. That will print out the values as devurandom was seeing.

cfleming11:09:52

I’ve never come up with a good heuristic for how long to keep handlers around after a :done message, so at the moment I tidy them up immediately. I guess I could use an LRU cache and keep up to 100 of them around while they’re still receiving messages or something, but that’s getting complicated and I haven’t needed to do it yet.

plexus13:09:34

I did notice working on the sideloader recently that that is an extreme case where the sideloader will respond with :done as soon as it is installed, but will then send messages to the client with the same id whenever it wants to request a resource. possibly in perpetuity

plexus13:09:57

might make more sense for it to never send :done, or only when you stop the sideloader again

plexus13:09:20

but that's not the only weird thing about the sideloader so maybe not something that concerns you much 😄

cfleming02:09:06

Yeah, in that case I’d definitely expect the :done message not to be received until you’re done with the sideloader.