Accidentally reproduced NPE in synchronizeNodes in combination with enterNestedEventLoop

John Hendrikx john.hendrikx at gmail.com
Thu Aug 22 07:37:52 UTC 2024


I think I figured out the reason why this fails.  The root cause lies in 
a misconception I've seen in a lot of FX code.

JavaFX uses a single event thread model, which ensures all structures 
are only ever accessed by a single thread.  This frees FX from having to 
do synchronization on almost every modification you make to properties 
or the scene graph.

However, in many areas it makes the assumption that such code will 
always run sequentially to completion without interruption, and uses 
instance fields judiciously to communicate things to deeper nested code 
or to code further down the line.  But code using instance fields in 
this way is not safe to re-enter (it is not reentrant-safe) without 
precautions -- sharing instance fields in this way safely can easily get 
as complicated as writing multi-threaded code.

A simple example that I saw in Parent's toFront code:

childrenTriggerPermutation= true;

try{

children.remove(node);

children.add(node);

} finally{

childrenTriggerPermutation= false;

}

The above code uses an instance field "childrenTriggerPermutation" to 
activate an optimization. The optimization will assume that the children 
are only re-arranged, and no new ones were added or removed.  However, 
"children" is an ObservableList, which means the user can register 
listeners on it, which do who knows what.  If such a listener modifies 
the children list in another way then the code is entered again, but the 
"childrenTriggerPermutation" optimization will still be enabled causing 
it to not notice the change the user did.

This problem is similar to the ChangeListener old value bug. When within 
a change listener you do another change (and so the same code is called 
**deeper** in the same stack), downstream change listeners will not 
receive the correct old values because the code is insufficiently 
reentrant-safe.  ExpressionHelper **tries** to mitigate some of these 
issues (for cases where listeners are added/removed reentrantly) by 
making copies of the listener list, but it does not handle this case.

Similarly, the bug I encountered in my original post is also such an 
issue.  While processing the children list changes, several 
**properties** are being manipulated.  Being properties, these can have 
listeners of their own that could trigger further modifications and, in 
complex enough programs, they may even re-enter the same class's code 
that is sharing instance fields in an unsafe way.  And that's exactly 
what is happening:

1. The children list change processing is registering the offset of the 
first changed child in the children list (called "startIdx") as an 
instance field -- this field is used as an optimization for updatePeer 
(so it doesn't have to check/copy all children).  It assumes the 
processing always finishes completely and it will get to the point where 
it sets "startIdx" but...

2. Before it sets "startIdx" but after the children list is already 
modified, it modifies several properties.  Being properties, these can 
have listeners, and as such this can trigger a cascade of further calls 
in complicated applications.

3. In this case, the cascade of calls included an 
"enterNestedEventLoop".  Pulses (and things like Platform#runLater) can 
be handled on such a nested loop, and FX decides that now is as good a 
time as any to handle a new pulse.

4. The pulse triggers updatePeer calls, among which is the Parent that 
is still (higher in the stack) midway its children list processing code.

5. The updatePeer code looks at "startIdx", the shared instance field 
that Parent uses for its optimizations.  This field is NOT modified 
yet.  The field indicates the first child that was modified, and the 
field is normally set to "children.size()" when there are no changes.  
That's also the case in this case still, and so updatePeer updates 
nothing at all.  An assertion later in this code then checks if 
children.size() == peer.children.size() which fails... a stack trace is 
thrown, and synchronizeSceneNodes() blows up with infinite NPE's.

I'm not entirely sure yet how to resolve this, and if it should be.

Perhaps the safest way would be to undo some of the 
optimizations/assumptions, and perhaps reoptimize them if there's a 
pressing need.

Another option would be to somehow delay listener callbacks until the 
code in Parent is in a safe state.

The option I like the least is to introduce yet another instance flag 
("processingListChange") and throwing an early exception if other code 
is entered that doesn't expect it...

--John


On 22/08/2024 05:24, John Hendrikx wrote:
>
> Hi List,
>
> This is a bit of a long post. I'm mainly wondering if I did something 
> wrong that FX should detect early, or if I'm doing nothing unusual and 
> FX should handle the case described below correctly.
>
> I encountered the bug where an NPE occurs in 
> Scene$ScenePulseListener#synchronizeNodes, and it is reproducable at 
> the moment. I'm not sure it is the same one others sometimes see, but 
> the version I encountered is prefaced with a failing assert (which may 
> easily get lost as synchronizeNodes will spam NPE's in your log, as it 
> doesn't recover).
>
> In Parent#validatePG it prints:
>
>     *** pgnodes.size validatePG() [1] != children.size() [2]
>
> And then throws an AssertionError with a long stacktrace.
>
>     java.lang.AssertionError: validation of PGGroup children failed
>         (stack trace omitted)
>
> Immediately after this, the NPE in synchronizeNodes starts to get 
> spammed, and the application never recovers.
>
> This seems to have something to do with nested event loops, as I 
> introduced a new one in the code involved.  When I remove the nested 
> event loop, there is no problem (it also initially works fine when the 
> application is just starting, only in a different situation, when 
> there is some user interaction via a keypress, does the bug trigger).
>
> The nested event loop is entered from a ChangeListener, which may be a 
> bit unusual.  The documentation of Platform#enterNestedEventLoop says:
>
> * This method must either be called from an input event handler or
>
> * from the run method of a Runnable passed to
>
> * {@link javafx.application.Platform#runLater Platform.runLater}.
>
> * It must not be called during animation or layout processing.
>
> It is also documented to throw an IllegalStateException if used 
> incorrectly.  That is however not happening, so I guess I'm using it 
> correctly...?  On the other hand, I'm not in an input event 
> handler.... the whole process is triggered by a keypress though, and 
> deep down in the AssertionError trace you can see:
>
>     at 
> com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
>     at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
>     at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
>     at javafx.event.Event.fireEvent(Event.java:198)
>     at javafx.scene.Scene$KeyHandler.process(Scene.java:4113)
>     at javafx.scene.Scene.processKeyEvent(Scene.java:2159)
>     at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2627)
>     at 
> com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:218)
>
> So IMHO technically I am in an input event handler...?
>
> Now the code does a LOT of stuff, which is going to make this tough to 
> analyze.  In short:
>
> - I'm displaying a Scene
> - The user indicates (with a keypress) they want to go to a different Pane
> - The previous Pane is created, and added to the scene as a child to a 
> TransitionPane that will cross-fade between the current Pane and the 
> new Pane
> - As soon as the new Pane becomes part of the Scene, lots of things 
> get triggered because the Scene property goes from null to the active 
> Scene:
>
>     at javafx.scene.Node.invalidatedScenes(Node.java:1075)
>     at javafx.scene.Node.setScenes(Node.java:1142) <-- the new Pane 
> will get a Scene assigned to it
>     at javafx.scene.Parent$2.onChanged(Parent.java:372) <-- the new 
> Pane has its Parent changed to the TransitionPane
>
> - Several parts of the new Pane listen (directly or indirectly) to a 
> change in Scene, as they only become "active" when the Node involved 
> is displayed (this happens with a Scene listener)
> - One of those things is some code that will create and populate a 
> ListView; this code uses a relatively fast query, but I had marked it 
> as something that should be done in the background as it sometimes 
> does take a bit too much time on the FX thread
>
> Now, how I normally do things in the background is to display an 
> automatically closing "Please wait" dialog potentially with a progress 
> bar on it (this dialog is actually invisible, unless sufficient time 
> passes, so in 99% of the cases when things respond fast, the user 
> never sees it, even though it was created and is there).  This 
> involves starting a nested event loop. This works marvelously 
> throughout this application (and has done so for years), and it is 
> used for almost every transition from one part of the application to 
> the next.  In all cases so far however I did this directly from an 
> event handler.
>
> So the main difference is that I'm trying to enter a nested event loop 
> from a ChangeListener (which deep down was triggered by an Event).  In 
> the AssertionError stack trace (which I will include at the end), 
> there is no layout or animation code **before** entering the nested 
> loop, although there is some layout code **after** it was entered.
>
> I can live with the fact that I may be using enterNestedEventLoop 
> incorrectly here, but in that case it should probably also detect this 
> incorrect use and throw the IllegalStateException.
>
> Technically, all this code is triggered while "adding" a Child to the 
> TransitionPane, so I suspect that is what the AssertionError is about 
> (it indicates the child count 1 != 2, which is what TransitionPane 
> has, one active pane, and just added to cross fade to).  Still, is 
> this really incorrect usage?
>
> I've included an annotated stack trace below.
>
> As it is quite reproducable, I can debug this further by adding 
> breakpoints/prints -- I'm just unsure where to start looking.
>
> --John
>
> java.lang.AssertionError: validation of PGGroup children failed
>   at javafx.scene.Parent.validatePG(Parent.java:243)
>   at javafx.scene.Parent.doUpdatePeer(Parent.java:201)
>   at javafx.scene.Parent$1.doUpdatePeer(Parent.java:109)
>   at 
> com.sun.javafx.scene.ParentHelper.updatePeerImpl(ParentHelper.java:78)
>   at 
> com.sun.javafx.scene.layout.RegionHelper.updatePeerImpl(RegionHelper.java:72)
>   at com.sun.javafx.scene.NodeHelper.updatePeer(NodeHelper.java:104)
>   at javafx.scene.Node.syncPeer(Node.java:721)
>   at 
> javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2396)
>   at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2542)
>   at com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:407)
>   at 
> java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
>   at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:406)
>   at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:436)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:575)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:352)
>   at 
> com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
>   at 
> com.sun.glass.ui.win.WinApplication._enterNestedEventLoopImpl(Native 
> Method)
>   at 
> com.sun.glass.ui.win.WinApplication._enterNestedEventLoop(WinApplication.java:211)
>   at 
> com.sun.glass.ui.Application.enterNestedEventLoop(Application.java:515)
>   at com.sun.glass.ui.EventLoop.enter(EventLoop.java:107)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.enterNestedEventLoop(QuantumToolkit.java:631)
>   at javafx.application.Platform.enterNestedEventLoop(Platform.java:301)
>   at 
> hs.mediasystem.util.javafx.SceneUtil.enterNestedEventLoop(SceneUtil.java:75) 
> <-- just my wrapper to trace slow calls, it delegates to Platform
>   at 
> hs.mediasystem.runner.dialog.DialogPane.showDialog(DialogPane.java:78)
>   at 
> hs.mediasystem.runner.dialog.Dialogs.showProgressDialog(Dialogs.java:163)
>   at hs.mediasystem.runner.dialog.Dialogs.runNested(Dialogs.java:103)
>   at 
> hs.mediasystem.plugin.home.HomeScreenNodeFactory.lambda$1(HomeScreenNodeFactory.java:123) 
>
>
> The above line is where the nested event loop is entered.  It starts 
> to display a "busy" dialog. A background task (on a new thread) will 
> create a ListView (this never actually happens, the AssertionError is 
> thrown immediately even with an empty task that just sleeps 10 seconds).
>
>   at 
> com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:360)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at 
> javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:80)
>   at 
> javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102)
>   at 
> javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:118)
>   at 
> javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:172)
>   at 
> javafx.scene.control.SelectionModel.setSelectedItem(SelectionModel.java:105)
>   at 
> javafx.scene.control.MultipleSelectionModelBase.lambda$new$0(MultipleSelectionModelBase.java:67)
>   at 
> com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:348)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at 
> javafx.beans.property.ReadOnlyIntegerPropertyBase.fireValueChangedEvent(ReadOnlyIntegerPropertyBase.java:78)
>   at 
> javafx.beans.property.ReadOnlyIntegerWrapper.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:102)
>   at 
> javafx.beans.property.IntegerPropertyBase.markInvalid(IntegerPropertyBase.java:114)
>   at 
> javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:148)
>   at 
> javafx.scene.control.SelectionModel.setSelectedIndex(SelectionModel.java:69)
>   at 
> javafx.scene.control.MultipleSelectionModelBase.select(MultipleSelectionModelBase.java:424)
>   at 
> javafx.scene.control.MultipleSelectionModelBase.select(MultipleSelectionModelBase.java:456)
>   at 
> hs.mediasystem.plugin.home.HomeScreenNodeFactory.lambda$3(HomeScreenNodeFactory.java:176) 
> <-- triggers the creation of a ListView
>   at javafx.beans.value.ObservableValue.lambda$0(ObservableValue.java:364)
>   at 
> com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
>   at 
> com.sun.javafx.binding.ConditionalBinding.conditionChanged(ConditionalBinding.java:53)
>   at com.sun.javafx.binding.Subscription.lambda$2(Subscription.java:63)
>   at 
> com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
>   at com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)
>   at 
> com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
>   at com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)
>   at 
> com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
>   at 
> com.sun.javafx.binding.FlatMappedBinding.invalidateAll(FlatMappedBinding.java:102)
>   at com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)
>   at 
> com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
>   at 
> com.sun.javafx.binding.FlatMappedBinding.invalidateAll(FlatMappedBinding.java:102)
>   at com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)
>   at 
> com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:348)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at 
> javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:80)
>   at 
> javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102)
>   at 
> javafx.scene.Node$ReadOnlyObjectWrapperManualFire.fireSuperValueChangedEvent(Node.java:1053) 
> <-- there is a listener on a Scene property
>   at javafx.scene.Node.invalidatedScenes(Node.java:1104)
>   at javafx.scene.Node.setScenes(Node.java:1142)
>   at javafx.scene.Parent.scenesChanged(Parent.java:772) <-- the new 
> Pane has a few nested Panes, so you see multiple Parent assignments
>   at javafx.scene.Node.invalidatedScenes(Node.java:1075)
>   at javafx.scene.Node.setScenes(Node.java:1142)
>   at javafx.scene.Parent.scenesChanged(Parent.java:772)
>   at javafx.scene.Node.invalidatedScenes(Node.java:1075)
>   at javafx.scene.Node.setScenes(Node.java:1142) <-- the Parent is 
> part of a Scene, so this new Pane also gets the same Scene assigned
>   at javafx.scene.Parent$2.onChanged(Parent.java:372)    <-- the new 
> Pane gets its parent assigned (TransitionPane)
>   at 
> com.sun.javafx.collections.TrackableObservableList.lambda$new$0(TrackableObservableList.java:45)
>   at 
> com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
>   at 
> com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
>   at 
> javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:239)
>   at 
> javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
>   at 
> javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
>   at 
> javafx.collections.ObservableListBase.endChange(ObservableListBase.java:211)
>   at 
> javafx.collections.ModifiableObservableListBase.add(ModifiableObservableListBase.java:162)
>   at 
> com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:319)
>   at 
> hs.mediasystem.util.javafx.ui.transition.TransitionPane.add(TransitionPane.java:94) 
> <-- new Pane is added to a TransitionPane that handles cross fade 
> between old and new Pane
>   at 
> hs.mediasystem.util.javafx.ui.transition.TransitionPane.addAtStart(TransitionPane.java:105)
>   at 
> hs.mediasystem.util.javafx.ui.transition.TransitionPane.add(TransitionPane.java:113)
>   at 
> hs.mediasystem.runner.presentation.ViewPort.updateChildNode(ViewPort.java:68) 
> <-- here it installs the new Pane to display
>   at 
> hs.mediasystem.runner.presentation.ViewPort.lambda$0(ViewPort.java:39)
>   at 
> com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
>   at com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)
>   at 
> com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)
>   at 
> com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
>   at 
> javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:111)
>   at 
> javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:118)
>   at 
> javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:172)
>   at 
> hs.mediasystem.presentation.ParentPresentation.navigateBack(ParentPresentation.java:39) 
> <-- this handles the keyPress (it was a "back" press)
>   at hs.mediasystem.util.expose.Expose.lambda$3(Expose.java:55)
>   at hs.mediasystem.util.expose.Trigger$1.run(Trigger.java:58)
>   at 
> hs.mediasystem.runner.RootPresentationHandler.tryRunAction(RootPresentationHandler.java:106)
>   at 
> hs.mediasystem.runner.RootPresentationHandler.handleActionEvent(RootPresentationHandler.java:81)
>   at 
> com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:247)
>   at 
> com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
>   at 
> com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
>   at 
> com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
>   at 
> com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
>   at 
> com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
>   at hs.mediasystem.util.javafx.SceneUtil.lambda$1(SceneUtil.java:101) 
> <-- this is just my Slow Event detection, and just delegates the event
>   at 
> com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
>   at 
> com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
>   at 
> com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
>   at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
>   at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
>   at hs.mediasystem.util.javafx.base.Events.dispatchEvent(Events.java:35)
>   at 
> hs.mediasystem.runner.action.InputActionHandler.handleKeyEvent(InputActionHandler.java:153) 
> <-- just code that handles a keypress that bubbled all the way to the top
>   at 
> hs.mediasystem.runner.action.InputActionHandler.onKeyPressed(InputActionHandler.java:138)
>   at 
> com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
>   at 
> com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
>   at 
> com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
>   at 
> com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
>   at 
> com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
>   at hs.mediasystem.util.javafx.SceneUtil.lambda$1(SceneUtil.java:101) 
> <-- this is just my Slow Event detection, and just delegates the event
>   at 
> com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
>   at 
> com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
>   at 
> com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
>   at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
>   at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
>   at javafx.event.Event.fireEvent(Event.java:198)
>   at javafx.scene.Scene$KeyHandler.process(Scene.java:4113)
>   at javafx.scene.Scene.processKeyEvent(Scene.java:2159)
>   at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2627)
>   at 
> com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:218)
>   at 
> com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:150)
>   at 
> java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
>   at 
> com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$1(GlassViewEventHandler.java:250)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
>   at 
> com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:249)
>   at com.sun.glass.ui.View.handleKeyEvent(View.java:542)
>   at com.sun.glass.ui.View.notifyKey(View.java:966)
>   at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
>   at 
> com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
>   at java.base/java.lang.Thread.run(Thread.java:1583)
>
> And the NPE exception:
>
> java.lang.NullPointerException: Cannot invoke 
> "javafx.scene.Node.getScene()" because "<local2>" is null
>     at 
> javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2395)
>     at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2542)
>     at com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:407)
>     at 
> java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
>     at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:406)
>     (rest of trace identical to the AssertionError one)
>
> And another variant of the NPE exception:
>
> java.lang.NullPointerException: Cannot invoke 
> "javafx.scene.Node.getScene()" because "<local2>" is null
>   at 
> javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2395)
>   at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2542)
>   at com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:407)
>   at 
> java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
>   at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:406)
>   at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:436)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:575)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)
>   at 
> com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:352)
>   at 
> com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
>   at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
>   at 
> com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
>   at java.base/java.lang.Thread.run(Thread.java:1583)
>
>
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20240822/32c2ee5d/attachment-0001.htm>


More information about the openjfx-dev mailing list