Fork me on GitHub
#cider
<
2022-02-22
>
jumar11:02:52

I upgraded my Emacs (spacemacs) today and all the packages and noticed cider got some nice improvements like downloading java sources automatically It also shows a nice red error dialog (not sure if this is something spacemacs specific). But the problem is that it now frequently freezes upon evaluation, possibly when there are some errors in the source buffer. So it can consume lot of CPU (let's say 800%) for minutes without doing anything. An example OOM I got after which it crashed: E.g. I

jumar11:02:30

I tried to profile it with async while it was doing a lot of work and apart from GC there's a lot of work done inside orchard

vemv11:02:47

you are using JDK8, correct? One line of research if checking if the error simply goes away by using a more recent JDK

vemv11:02:33

And another is using JVM flags to avoid OOMs, here are mine (for a 64GB MBP) https://github.com/reducecombine/.lein/blob/f01aec663ff4ac8f8e83f8eca93a13b2cf99aa4b/profiles.clj#L10-L43 People would be surprised by how many issues go away with these flags - Clojure can be hungry in general and it can show in arbitrarily many libs

vemv11:02:17

will try to make sense of the flamegraph anyway 🙏 maybe something is problematically lazy

jumar11:02:06

Thanks for the prompt response. I'm using JDK 8 and I'm a bit stuck with this for a while (a legacy app). I haven't tuned it much but it's got 1GB heap (to mimic production settings). I'll keep an eye on it and will see if this becomes a frequent issue.

jumar11:02:43

From your settings, I would say only setting large Xmx and Xss could have a positive effect.

vemv12:02:59

XX:CompressedClassSpaceSize also (although not so much for this specific problem) In general I don't recommend mimicking production settings. A dev repl can have much more in it e.g. cider-nrepl, refactor-nrepl, kondo (when used as a jvm lib) etc so that already makes the memory requirements different Perhaps in CI it's more apt

vemv12:02:34

Another line of research is that gc overhead limit exceed is an unusual wording, perhaps it's specific to a given GC? Not sure if you are explicitly choosing one. Swapping impls might do the trick. Also in the second screenshot I'd need to be able to read what is said in the rightmost panes

vemv12:02:00

Hopefully this will fix it, at the same time everything I said stands :) https://github.com/clojure-emacs/orchard/pull/154 working on a 1GB jvm is not guaranteed

jumar15:02:40

Thanks for the prompt action! Note that gc overhead limit exceed is a very common thing nothing special: The rule tends to be something like : >  too much time (e.g. 98%) of a time interval (e.g.  no less than 1 minute) during which multiple (e.g. at least N) full GC cycles have run was spent in  stop-the world GC. (https://groups.google.com/u/1/g/mechanical-sympathy/c/TpyjsaPhM5U)

jumar15:02:11

Also, the app has been working with 1gb just fine for a couple of years. I'm adding a couple more pictures, the second one is at the very far right but it also consumed only a tiny piece of CPU time

jumar15:02:10

That said, I'm really impressed by the prompt fix you did 🙂

jumar05:02:24

(If you saw it already, forget what I said above - it was a stupid mistake on my side and I deleted the messages)

vemv07:02:16

I guess I was lucky because I didn't see them :)

vemv07:02:54

>  too much time (e.g. 98%) of a time interval (e.g.  no less than 1 minute)  what I find peculiars about this is that memory hasn't literally run out, but rather an heuristic is being hit

vemv07:02:26

> Also, the app has been working with 1gb just fine for a couple of years. But tooling changes, so it's a bet that can fail at any time (as it has for me with various other tooling or libs)

vemv07:02:17

> That said, I'm really impressed by the prompt fix you did cheers cider The changes made it to the latest CIDER snapshot, would be curious as to whether they make a difference

jumar08:02:01

Snapshots are published to MELPA? That is I can just upgrade the emacs cider package?

vemv08:02:57

depends on what melpa are you using

vemv08:02:13

a stable release will be cut within a week

enn16:02:06

is there an easy way to change the REPL a given buffer is associated with? I know that if I kill all but one REPL, then all remaining Clojure buffers will compile to the last REPL standing, regardless of their previous association. But I’d like to be able to change the REPL for a buffer without killing its old REPL.

magnars07:02:12

C-c C-s b is sesman-link-with-buffer which does this.

enn15:02:20

thank you!

eggsyntax19:02:52

@bozhidar or anyone else, have you ever looked into (or are you aware of anyone experimenting with) having https://mikelevins.github.io/posts/2020-12-18-repl-driven/ in CIDER? Or possibly at the nREPL level? I’ve been doing a bit of reading about the benefits of the breakloop & starting to think about what it would take to have one in Clojure.

mkvlr19:02:56

@U5NCUG8NR has been building https://github.com/IGJoshua/farolero and I believe nrepl integration is on the roadmap

👀 2
🙏 2
Joshua Suskalo19:02:49

It is definitely on the roadmap. Unfortunately it will never come all the way to what common lisp can do because I can't inject usage of farolero into clojure core, and I'm not currently aware of a way to hijack a thread when an exception is thrown before it unwinds, so it'll be limited to where you place wrap-exceptions calls and similar.

Joshua Suskalo19:02:34

Any library or application using farolero for its stuff will get a breakloop-style debugging experience for stuff that they've written to support that workflow, though.

eggsyntax19:02:39

Very cool! As awesome as it would be to have full a CL condition system in Clojure, I was imagining something lighter-weight built on CIDER’s existing debugger — IIUC the CIDER debugger https://docs.cider.mx/cider/debugging/debugger.html#using-the-debugger before running it, and I was imagining this could similarly wrap everything in a try/catch, and drop to the debugger whenever it caught an exception. > I’m not currently aware of a way to hijack a thread when an exception is thrown before it unwinds I wonder whether you could temporarily capture the stack before evaluating each expression, and then if the expression resulted in an exception, restore the saved stack and drop to the breakloop. That might result in user-visible side effects happening twice, but seems like it’d still be very useful. I may be being painfully naive here; like I said I’ve just started to think about this problem.

eggsyntax19:02:11

I’m basically wondering what if anything I could accomplish in this vein as an experienced Clojure programmer with a couple of weeks between jobs to devote to it.

Joshua Suskalo19:02:40

That "capture a stack" is continuations, and the JVM does not currently expose a way to do that.

Joshua Suskalo19:02:00

and try/catch misses the bit where you have to catch things before they unwind, although just wrapping all functions in this type of try/catch and then retrying the function may be possible if the cider debugger already does this, but I want to note that the cider debugger doesn't wrap most functions, just the ones you explicitly wrap.

eggsyntax19:02:23

> That “capture a stack” is continuations, and the JVM does not currently expose a way to do that. Gotcha, makes sense, not something I’ve ever looked into with the JVM. > although just wrapping all functions in this type of try/catch and then retrying the function may be possible That’s definitely what I was imagining. > the cider debugger doesn’t wrap most functions, just the ones you explicitly wrap. cider-debug-defun-at-point is what I’m thinking of; per the CIDER docs that “will insert as many breakpoints as possible into the form”.

eggsyntax19:02:22

It does certainly sound like there’s no easy intervention that would enable the true CL breakloop with restarts, but it seems like “break to debugger on exception” would have some value on its own, and I’m hoping that the “wrap every single expression and subexpression with try/catch” could prevent the unwinding to keep the state of the stack as local as possible. Might still be being too naive though 😆

Joshua Suskalo19:02:43

right, my main thought is that often when an exception appears out of nowhere and it would be the most useful to have this kind of breakloop is when it's already 10 layers deep in the callstack and now you have to go find the function that threw the exception, mark it as a debugged function, and then retry your whole thing up to that point to get it to fire.

Joshua Suskalo19:02:22

That said, I don't want to discourage you from trying, if you're able to get something that works for that usecase I think it'd help me out quite a bit in my personal workflow.

eggsyntax19:02:00

> right, my main thought is that often when an exception appears out of nowhere and it would be the most useful to have this kind of breakloop is when it’s already 10 layers deep in the callstack and now you have to go find the function that threw the exception, mark it as a debugged function, and then retry your whole thing up to that point to get it to fire. My ideal would be to call something like toggle-debug-on-exception and then every expression would be transparently wrapped. > That said, I don’t want to discourage you from trying, if you’re able to get something that works for that usecase I think it’d help me out quite a bit in my personal workflow. Not discouraging at all! This is super-helpful info that’ll save me at least one big blind alley, and I really appreciate you taking time to talk about it. It definitely doesn’t have nearly as much potential as Farolero! But I’m hoping that it could be done in a relatively lightweight way and wouldn’t require writing/rewriting the code to specifically take advantage of it. Worst case it’ll be a fun way to spend a couple of weeks even if nothing comes of it 😆

mkvlr19:02:37

curious if project loom will lift these limitations

1
Joshua Suskalo20:02:31

loom has continuations at a low level, but I don't know how much that would actually help if there isn't a reasonable way to do a "break on exception" style facility. I know that some debuggers can do this, so maybe it's feasible?

Joshua Suskalo20:02:02

Honestly my main thought would be that I suspect that normal threads would dodge this by not actually having continuations, since they're only needed on the virtual threads.

mkvlr20:02:36

can the uncaught exception handler be such a facility?

eggsyntax20:02:45

> can the uncaught exception handler be such a facility? https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Thread.UncaughtExceptionHandler.html the stack would be fully unwound by the time it hit the uncaught exception handler, so it would be of limited use for something like a debugger.

👍 1
eggsyntax22:02:14

Oh, hmm. I see @U0GN0S72R has already created https://github.com/gfredericks/debug-repl, implemented as nREPL middleware. I think that means that automatically dropping to a debug repl on exception would “just” require automatically wrapping each call (or maybe even just the top-level call?) in the https://github.com/gfredericks/debug-repl/blob/master/src/com/gfredericks/debug_repl.clj#L256 macro. Although I’m a bit confused as to how/why debug-repl is middleware, since at least in its https://github.com/gfredericks/debug-repl/blob/master/README.md the breakpoints are being set manually. So playing with that will be my next step 🙂

eggsyntax23:02:28

Oh, I think I see, maybe it’s middleware https://nrepl.org/nrepl/design/middleware.html#sessions its own set of dynamic vars (eg *1, *e).

gfredericks23:02:36

My vague recollection is it's more fundamental than that

gfredericks23:02:05

It's gnarly code though. Can't figure out at a glance what problems it was trying to solve

eggsyntax00:02:29

Ah, gotcha. It might make it extra-useful for what I’m trying to do anyhow, not sure yet. I’ll play with it. Thanks for making it!

👍 1
eggsyntax02:02:48

@U0GN0S72R wow, that really is some genuinely gnarly code 😅

eggsyntax02:02:04

Super cool though. I’m having fun trying to reverse engineer it a bit 😁

gasrulle19:02:30

Is there a way to evaluate all forms within a rich comment? As it is now I have to eval each one by one when i start the REPL. I can evaluate the whole buffer, but as far as I can see, this does not apply to things defined in rich comments (and that’s maybe how it supposed to be)?

eggsyntax20:02:06

I usually wrap them in a do if I think I’m going to frequently want to eval them all.

practicalli-johnny20:02:15

This is exactly how I use rich comment forms. I can safely evaluate the namespace without worrying about code I do not want to load in the comment form

👍 2
eggsyntax20:02:53

> I usually wrap them in a `do` ie

(comment
  (do
    (foo)
    (bar)
    (baz)
    ))
so that I can then evaluate them separately or all at once as desired.

gasrulle20:02:29

@U077BEWNQ thanks for the quick reply! I’ll try that.

👍 1
gasrulle20:02:50

@U05254DQM yeah, it makes sense (just trying to get my head around all the things about Clojure :)

👍 1
Joshua Suskalo21:02:03

Is there any way with cider's debugger to go up in the callstack and inspect the state of locals or similar, without unwinding?

eggsyntax22:02:37

You can definitely inspect locals without unwinding, and you can pop out a level (although from that latter I don’t think you can get back inward). Locals being shown on left:

eggsyntax22:02:31

This bit shows the debugger commands, which are documented https://docs.cider.mx/cider/debugging/debugger.html:

jumar05:02:14

Not sure what you mean by unwinding but every now and then, I wish it was possible to step out of the instrumented function and continue debugging higher up the stack. I don't think this is doable today. Like here, If I instrument only baz I would like to be able to jump out and continue stepping through the code in bar and possibly foo https://github.com/jumarko/clojure-experiments/blob/master/src/clojure_experiments/debugging.clj#L6

(defn baz [z]
  (let [zz (+ 10 z)
        zzs (repeat zz z)]
    (mapv inc zzs)))

(defn bar [y]
  (let [yy (+ 5 y)]
    (baz yy)))

(defn foo [x]
  (bar (inc x)))

(comment
  (foo 3)
  ,)

jumar05:02:53

The other thing that doesn't really work for me is *s*tacktrace. When I press s nothing happens, at least I don't see anything - I though it would print the stacktrace to stdout.

jumar05:02:42

I'm not sure how in works but it apparently is able to step through a function which wasn't explicitly instrumented. I would love to see a variant of out that would have the same capability.

jumar06:02:21

Ok, I thought that perhaps s isn't working because I use spacemacs in evil mode. But it still doesn't seem to do anything after I switch to emacs mode. I'm wondering where the "s" shortcut is defined - it seems most of them are here, but not "s": https://github.com/clojure-emacs/cider/blob/master/cider-debug.el#L400-L424

eggsyntax14:02:48

@U06BE1L6T working fine for me in spacemacs/evil (CIDER 1.1.1), so I don’t think that’s your problem. The binding to s might actually be https://github.com/clojure-emacs/cider/blob/master/cider-debug.el#L191 in cider-debug-prompt-commands.

jumar14:02:33

@U077BEWNQ so it actually prints the stacktrace in the REPL buffer or what exactly it does?

eggsyntax14:02:57

For me it pops up the *cider-error* buffer and shows it there.

jumar14:02:21

I see, thanks - have no idea why it doesn't work for me 😞 Ah, maybe... Oh yes! It's a sideffect of me disabling the error buffer via cider-show-error-buffer set to never. (I found it quite slow and annoying) Definitely unintuitive!

🎉 1
Joshua Suskalo15:02:07

in works by recompiling the function with instrumentation before you call it

Joshua Suskalo15:02:24

at least I'm fairly sure

Joshua Suskalo15:02:46

Out can't do that because you can't recompile code that's already been partially run

jumar15:02:54

Yes, makes sense.