Accidentally reproduced NPE in synchronizeNodes in combination with enterNestedEventLoop

John Hendrikx john.hendrikx at gmail.com
Fri Aug 23 01:38:26 UTC 2024


Small sample program that triggers an assert in Parent:

@Override

publicvoidstart(Stage primaryStage) {

HBox hbox = newHBox();

Scene scene = newScene(hbox);

Button button1 = newButton("Hi");

Button button2 = newButton("World");

Button button3 = newButton("!");

hbox.getChildren().addAll(button1, button2);

hbox.getChildren().addListener(newInvalidationListener() {

@Override

publicvoidinvalidated(Observable observable) {

if(!hbox.getChildren().contains(button3)) {

hbox.getChildren().addAll(button3, button3);

}

}

});

button1.toFront();

primaryStage.setScene(scene);

primaryStage.show();

}

--John

On 22/08/2024 09:37, John Hendrikx wrote:
>
> 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/20240823/795b48f9/attachment-0001.htm>


More information about the openjfx-dev mailing list