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