Fork me on GitHub
#cider
<
2024-03-23
>
jpmonettas20:03:04

Hi everybody, I upgraded to the latest cider and I can't start my javafx application anymore. Stacktrace on the 🧵

jpmonettas20:03:34

Exception in thread "JavaFX Application Thread" java.lang.NoClassDefFoundError: Could not initialize class javafx.stage.Screen
	at com.sun.javafx.tk.quantum.QuantumToolkit.initSceneGraph(QuantumToolkit.java:331)
	at com.sun.javafx.tk.quantum.QuantumToolkit.runToolkit(QuantumToolkit.java:374)
	at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$startup$10(QuantumToolkit.java:290)
	at com.sun.glass.ui.Application.lambda$run$1(Application.java:155)
	at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
	at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$10(GtkApplication.java:263)
	at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = clojure-agent-send-off-pool-0 [in thread "clojure-agent-send-off-pool-0"]
	at com.sun.glass.ui.Application.checkEventThread(Application.java:447)
	at com.sun.glass.ui.Screen.setEventHandler(Screen.java:367)
	at com.sun.javafx.tk.quantum.QuantumToolkit.setScreenConfigurationListener(QuantumToolkit.java:743)
	at javafx.stage.Screen.<clinit>(Screen.java:74)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:421)
	at java.base/java.lang.Class.forName(Class.java:412)
	at orchard.java$class_info_STAR_$fn__5507.invoke(java.clj:325)
	at orchard.java$class_info_STAR_.invokeStatic(java.clj:325)
	at orchard.java$class_info_STAR_.invoke(java.clj:321)
	at orchard.java$class_info.invokeStatic(java.clj:376)
	at orchard.java$class_info.invoke(java.clj:368)
	at cider.nrepl$warmup_orchard_caches_BANG_.invokeStatic(nrepl.clj:71)
	at cider.nrepl$warmup_orchard_caches_BANG_.invoke(nrepl.clj:53)
	at cider.nrepl$fn__6301.invokeStatic(nrepl.clj:77)
	at cider.nrepl$fn__6301.invoke(nrepl.clj:76)
	at clojure.core$binding_conveyor_fn$fn__5852.invoke(core.clj:2047)
	at clojure.lang.AFn.call(AFn.java:18)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
	at java.base/java.ut

jpmonettas20:03:36

looks like this cider.nrepl$warmup_orchard_caches_BANG_ is doing something that can only be done in a javafx thread

jpmonettas20:03:19

rolling back to 20231025.508 which uses cider-nrepl "0.41.0" doesn't show this problem

vemv06:03:15

This Orchard call is the culprit as it performs class initialization https://github.com/clojure-emacs/orchard/blob/97c04e67bfd4183adf882b42829da0981d4c0828/src/orchard/java.clj#L325-L328 It's very old code, I'm surprised it didn't fail sooner Fix soon

🙏 1
jpmonettas21:03:11

hey, thanks @U45T93RA6, I'll try it right away

jpmonettas21:03:06

hmmm, still the same error :

;;  Startup: /usr/local/bin/clojure -Sdeps \{\:deps\ \{nrepl/nrepl\ \{\:mvn/version\ \"1.1.1\"\}\ cider/cider-nrepl\ \{\:mvn/version\ \"0.47.1\"\}\ refactor-nrepl/refactor-nrepl\ \{\:mvn/version\ \"3.10.0\"\}\}\ \:aliases\ \{\:cider/nrepl\ \{\:main-opts\ \[\"-m\"\ \"nrepl.cmdline\"\ \"--middleware\"\ \"\[refactor-nrepl.middleware/wrap-refactor\,cider.nrepl/cider-middleware\]\"\]\}\}\} -M:dev:dev-tools:storm:cider/nrepl

Mar 24, 2024 6:23:46 PM com.sun.javafx.application.PlatformImpl startup
WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @b121a86'
Exception in thread "JavaFX Application Thread" java.lang.NoClassDefFoundError: Could not initialize class javafx.stage.Screen
	at com.sun.javafx.tk.quantum.QuantumToolkit.initSceneGraph(QuantumToolkit.java:331)
	at com.sun.javafx.tk.quantum.QuantumToolkit.runToolkit(QuantumToolkit.java:374)
	at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$startup$10(QuantumToolkit.java:290)
	at com.sun.glass.ui.Application.lambda$run$1(Application.java:155)
	at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
	at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$10(GtkApplication.java:263)
	at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = clojure-agent-send-off-pool-0 [in thread "clojure-agent-send-off-pool-0"]
	at com.sun.glass.ui.Application.checkEventThread(Application.java:447)
	at com.sun.glass.ui.Screen.setEventHandler(Screen.java:367)
	at com.sun.javafx.tk.quantum.QuantumToolkit.setScreenConfigurationListener(QuantumToolkit.java:743)
	at javafx.stage.Screen.<clinit>(Screen.java:74)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:421)
	at java.base/java.lang.Class.forName(Class.java:412)
	at orchard.java$class_info_STAR_$fn__5507.invoke(java.clj:326)
	at orchard.java$class_info_STAR_.invokeStatic(java.clj:325)
	at orchard.java$class_info_STAR_.invoke(java.clj:321)
	at orchard.java$class_info.invokeStatic(java.clj:378)
	at orchard.java$class_info.invoke(java.clj:370)
	at cider.nrepl$warmup_orchard_caches_BANG_.invokeStatic(nrepl.clj:71)
	at cider.nrepl$warmup_orchard_caches_BANG_.invoke(nrepl.clj:53)
	at cider.nrepl$fn__6301.invokeStatic(nrepl.clj:77)
	at cider.nrepl$fn__6301.invoke(nrepl.clj:76)
	at clojure.core$binding_conveyor_fn$fn__5852.invoke(core.clj:2047)
	at clojure.lang.AFn.call(AFn.java:18)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
	at java.base/java.ut

vemv21:03:01

thanks! I had added a catch NoClassDefFoundError , I should have added ExceptionInInitializerError instead/also New release tomorrow, thanks for your help

jpmonettas21:03:51

np, thank you for looking at it! Let me know and I can try it from github so you don't need to release stuff to Clojars

vemv21:03:33

sure! repeat https://github.com/clojure-emacs/orchard/pull/234 catching the additional class. follow the make install instructions first for Orchard, then for cider-nrepl (in that order, and following each readme's notes) thanks 🤝

jpmonettas21:03:48

great, I'll try it and let you know

jpmonettas23:03:50

@U45T93RA6 This ended up being much harder than I expected, so giving up for today. It feels to me like it has to do with some classloders and when static initializers run issues, but not sure, I don't know a lot about this stuff. The catch you added ( NoClassDefFoundError ) was ok, it is catching the classFor correctly. The problem has to do with some classes initialization. It is not the Screen class, that is kind of misleading I think. I first tried logging that orchard code you were modifying with something like :

(try
  (println "@@@@@ About to load " class (.getName (Thread/currentThread)))
  (let [c (Class/forName (str class))]
    (println "@@@@ loaded " c)
    c)
  (catch Exception _)
  (catch NoClassDefFoundError _ (println "@@@@ NoClassDefFoundError"))
  (catch LinkageError _))
But the results were even more confusing. So I went ahead and created the smallest repo I found that reproduce the issue. https://github.com/jpmonettas/cider-nrepl-bug-repro

jpmonettas12:03:32

just adding more info to the thread, I'm testing with the cider-nrepl-bug-repro project : • if you comment out (Class/forName ...) in orchard it works • but if instead you (try ... (catch Throwable)) then it doesn't work, which is weird that you can't catch that exception coming from the static initializer

jpmonettas12:03:06

ok, some progress here. What I figured out is if you initialize any javafx class without the toolkit initialized (which cider-nrepl is doing), then you will put the JavaFX system into a bad state :face_with_rolling_eyes:, and no matter if you try to initialize the toolkit after, it won't work anymore. You can reproduce this easily without cider-nrepl or anything, just with plain JavaFX

jpmonettas12:03:57

so on one side this is like a bug in JavaFX, but on the other side it is a bug on cider-nrepl warmup system that is assuming you can initialize any class, which will break systems that require stuff to be done before a class can be initialized

jpmonettas13:03:16

for now I patched the orchard.java/class-info* try/catch with :

(try
  (let [class-str (str class)]
    (when-not (string/starts-with? class-str "javafx")
      (Class/forName class-str)))
  (catch Throwable _))
which solves the issue, but that is as hacky as it gets

vemv13:03:02

but on the other side it is a bug on cider-nrepl warmup system that is assuming you can initialize any class, which will break systems that require stuff to be done before a class can be initializedIf I (immediately) could, I'd just pass the false second argument to Class/forName (we already do in other places). But that happens to break defrecord/deftype analysis. I think that the approach I'll take is: • pass true if the classname has a backing .clj file/resource that can be inferred and exists • pass false otherwise ◦ no class initialized, no issues for us or you Thanks much for the observations - they sure have helped :)

jpmonettas13:03:38

I tried passing false to it already, but it fails further down, when something else touches that class, which ends up running the static initialization code

vemv13:03:21

I reckon I could do what I outlined and then hunt for the second culprit And will make sure to turn your repro into a test case 👍

jpmonettas13:03:46

I can help with that, let me see...

🙌 1
jpmonettas13:03:31

hmmm that is very weird, I tried passing false now and it works :thinking_face:

jpmonettas13:03:35

Class/forName needs a classloader, I'm running with this now (Class/forName class false @clojure.lang.Compiler/LOADER) but not sure

jpmonettas13:03:52

dang, I was about to create a PR but I see that passing false there breaks a trillon tests

jpmonettas13:03:49

so I'm leaving the thread with that, let me know if you need me to try something else!

vemv13:03:45

We normally use (.getContextClassLoader (Thread/currentThread)) Only two tests should break (amplified by the matrix) - related to deftype/defrecord Detecting a .clj / .cljc resource backing the class for deciding true/false is a fairly small task. But I'd be totally fine with tackling it myself

jpmonettas13:03:53

let me try that classloader change

jpmonettas13:03:33

here I'm leaving the PR, just for reference

jpmonettas13:03:39

it says like 122 failures

vemv13:03:33

Weird, was 2 yesterday

vemv13:03:05

Maybe don't drop the (str

jpmonettas13:03:28

oh that is my bad, sorry

jpmonettas13:03:40

so many changes forgot that one

jpmonettas13:03:14

yeah, now only 2 are failing

jpmonettas13:03:40

ok, leaving this for now! will keep using the patched orchard version in the meantime

jpmonettas13:03:31

OMG, now I'm trying again and the further down issue is back :rolling_on_the_floor_laughing:facepalm, I think it wasn't happening because I forgot that string there

jpmonettas13:03:01

Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: Toolkit not initialized [in thread "clojure-agent-send-off-pool-0"]
	at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:436)
	at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:431)
	at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:724)
	at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:686)
	at javafx.scene.control.Control.<clinit>(Control.java:98)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:534)
	at java.base/java.lang.Class.forName(Class.java:513)
	at clojure.lang.RT.classForName(RT.java:2229)
	at clojure.lang.RT.classForName(RT.java:2238)
	at clojure.lang.Compiler.maybeResolveIn(Compiler.java:8130)
	at clojure.core$ns_resolve.invokeStatic(core.clj:4386)
	at clojure.core$ns_resolve.invokeStatic(core.clj:4375)
	at clojure.core$resolve.invokeStatic(core.clj:4388)
	at clojure.core$resolve.invoke(core.clj:4388)
	at orchard.java.parser_utils$resolve.invokeStatic(parser_utils.clj:96)
	at orchard.java.parser_utils$resolve.invoke(parser_utils.clj:91)
	at orchard.java.parser_utils$source_path.invokeStatic(parser_utils.clj:108)
	at orchard.java.parser_utils$source_path.invoke(parser_utils.clj:105)
	at orchard.java.parser$source_info.invokeStatic(parser.clj:229)
	at orchard.java.parser$source_info.invoke(parser.clj:220)
	at clojure.lang.AFn.applyToHelper(AFn.java:154)
	at clojure.lang.AFn.applyTo(AFn.java:144)
	at clojure.lang.Var.applyTo(Var.java:707)
	at clojure.core$apply.invokeStatic(core.clj:667)
	at clojure.core$apply.invoke(core.clj:662)
	at orchard.java$source_info_STAR_.invokeStatic(java.clj:124)
	at orchard.java$source_info_STAR_.doInvoke(java.clj:101)
	at clojure.lang.RestFn.invoke(RestFn.java:411)
	at orchard.java$source_info.invokeStatic(java.clj:139)
	at orchard.java$source_info.invoke(java.clj:135)
	at orchard.java$class_info_STAR_.invokeStatic(java.clj:334)
	at orchard.java$class_info_STAR_.invoke(java.clj:321)
	at orchard.java$class_info.invokeStatic(java.clj:379)
	at orchard.java$class_info.invoke(java.clj:371)
	at cider.nrepl$warmup_orchard_caches_BANG_.invokeStatic(nrepl.clj:71)
	at cider.nrepl$warmup_orchard_caches_BANG_.invoke(nrepl.clj:53)
	at cider.nrepl$fn__6301.invokeStatic(nrepl.clj:77)
	at cider.nrepl$fn__6301.invoke(nrepl.clj:76)
	at clojure.core$binding_conveyor_fn$fn__5852.invoke(core.clj:2047)
	at clojure.lang.AFn.call(AFn.java:18)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
	at java.base/java.util.concu

jpmonettas13:03:04

that is the new place

jpmonettas13:03:21

the parser_utils resolve

vemv13:03:28

I'll check it in detail. I don't immediately know if we can or can't generically avoid this issue. cider.nrepl$warmup_orchard_caches uses false, always. So the class looks OK at that point. Then I can't imagine how we'd use resolve without evaluating stuff. Maybe it is possible not to use resolve. If that wasn't possible, we'd have to blacklist the class and wait for https://github.com/clojure-emacs/orchard/issues/211 (very much necessary anyway)

vemv13:03:10

We also could have a system prop for allowing users to extend the blacklist

jpmonettas13:03:23

yeah, that would be a way

jpmonettas13:03:39

looks like it isn't going to be needed anymore?

jpmonettas14:03:59

anyway, won't bother you anymore, you probably have other stuff to do and this probably needs some hammock time

vemv14:03:18

That would be neat :) If you are willing to further play with this LMK Indeed it's a busy week over here. I sense that it could all be pretty quick to solve, so if you're up to it, that could be fun as well Either way, thanks for the hints, it's almost as if you had omniscient debugging skills ;)

1
jpmonettas14:03:05

I don't think I know enough to make good decisions about how to fix this for real. I can hack it for sure, but you probably are going to make a much informed solution. Let me know if you need help debugging something or to try something on my env

👍 1