<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>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.<br>
</p>
<p>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.</p>
<p>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.</p>
<p>A simple example that I saw in Parent's toFront code:</p>
<div style="background-color:#ffffff;padding:0px 0px 0px 2px;">
<div
style="color:#000000;background-color:#ffffff;font-family:"Consolas";font-size:11pt;white-space:pre;"><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000c0;">childrenTriggerPermutation</span><span
style="color:#000000;"> = </span><span
style="color:#0000a0;font-weight:bold;">true</span><span
style="color:#000000;">;</span></p><p style="margin:0;"><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">try</span><span
style="color:#000000;"> {</span></p><p style="margin:0;"><span
style="color:#000000;"> </span><span
style="color:#0000c0;">children</span><span style="color:#000000;">.remove(node);</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000c0;">children</span><span style="color:#000000;">.add(node);</span></p><p
style="margin:0;"><span style="color:#000000;"> } </span><span
style="color:#0000a0;font-weight:bold;">finally</span><span
style="color:#000000;"> {</span></p><p style="margin:0;"><span
style="color:#000000;"> </span><span
style="color:#0000c0;">childrenTriggerPermutation</span><span
style="color:#000000;"> = </span><span
style="color:#0000a0;font-weight:bold;">false</span><span
style="color:#000000;">;</span></p><p style="margin:0;"><span
style="color:#000000;"> }</span></p></div>
</div>
<p></p>
<p>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.</p>
<p>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.<br>
</p>
<p>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:</p>
<p>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...<br>
</p>
<p>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.</p>
<p>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.</p>
<p>4. The pulse triggers updatePeer calls, among which is the Parent
that is still (higher in the stack) midway its children list
processing code.</p>
<p>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.</p>
<p>I'm not entirely sure yet how to resolve this, and if it should
be.</p>
<p>Perhaps the safest way would be to undo some of the
optimizations/assumptions, and perhaps reoptimize them if there's
a pressing need.</p>
<p>Another option would be to somehow delay listener callbacks until
the code in Parent is in a safe state.</p>
<p>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...</p>
<p>--John<br>
</p>
<p><br>
</p>
On 22/08/2024 05:24, John Hendrikx wrote:<br>
<blockquote type="cite"
cite="mid:efc29c6d-9b12-408b-9328-af181c42ba5d@gmail.com">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<p>Hi List,</p>
<p>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.<br>
</p>
<p>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).</p>
<p>In Parent#validatePG it prints:</p>
<p> *** pgnodes.size validatePG() [1] != children.size() [2]</p>
<p>And then throws an AssertionError with a long stacktrace.<br>
</p>
<p> java.lang.AssertionError: validation of PGGroup children
failed<br>
(stack trace omitted)<br>
</p>
<p>Immediately after this, the NPE in synchronizeNodes starts to
get spammed, and the application never recovers.<br>
</p>
<p>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).</p>
<p>The nested event loop is entered from a ChangeListener, which
may be a bit unusual. The documentation of
Platform#enterNestedEventLoop says:</p>
<div style="background-color:#ffffff;padding:0px 0px 0px 2px;">
<div
style="color:#000000;background-color:#ffffff;font-family:"Consolas";font-size:11pt;white-space:pre;"><p
style="margin:0;"><span style="color:#3f5fbf;"> * This method must either be called from an input event handler or</span></p><p
style="margin:0;"><span style="color:#3f5fbf;"> * from the run method of a Runnable passed to</span></p><p
style="margin:0;"><span style="color:#3f5fbf;"> * </span><span
style="color:#3f3fbf;">{@link javafx.application.Platform#runLater Platform.runLater}</span><span
style="color:#3f5fbf;">.</span></p><p style="margin:0;"><span
style="color:#3f5fbf;"> * It must not be called during animation or layout processing.</span></p></div>
</div>
<p>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:</p>
<p> at
com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)<br>
at
com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)<br>
at
com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)<br>
at javafx.event.Event.fireEvent(Event.java:198)<br>
at javafx.scene.Scene$KeyHandler.process(Scene.java:4113)<br>
at javafx.scene.Scene.processKeyEvent(Scene.java:2159)<br>
at
javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2627)<br>
at
com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:218)<br>
<br>
So IMHO technically I am in an input event handler...?</p>
<p>Now the code does a LOT of stuff, which is going to make this
tough to analyze. In short:</p>
<p>- I'm displaying a Scene<br>
- The user indicates (with a keypress) they want to go to a
different Pane<br>
- 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<br>
- 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:<br>
<br>
at javafx.scene.Node.invalidatedScenes(Node.java:1075)<br>
at javafx.scene.Node.setScenes(Node.java:1142)
<-- the new Pane will get a Scene assigned to it<br>
at javafx.scene.Parent$2.onChanged(Parent.java:372)
<-- the new Pane has its Parent changed to the TransitionPane<br>
<br>
- 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)<br>
- 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</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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?</p>
<p>I've included an annotated stack trace below.</p>
<p>As it is quite reproducable, I can debug this further by adding
breakpoints/prints -- I'm just unsure where to start looking.<br>
</p>
<p>--John</p>
<p>java.lang.AssertionError: validation of PGGroup children failed<br>
at javafx.scene.Parent.validatePG(Parent.java:243)<br>
at javafx.scene.Parent.doUpdatePeer(Parent.java:201)<br>
at javafx.scene.Parent$1.doUpdatePeer(Parent.java:109)<br>
at
com.sun.javafx.scene.ParentHelper.updatePeerImpl(ParentHelper.java:78)<br>
at
com.sun.javafx.scene.layout.RegionHelper.updatePeerImpl(RegionHelper.java:72)<br>
at
com.sun.javafx.scene.NodeHelper.updatePeer(NodeHelper.java:104)<br>
at javafx.scene.Node.syncPeer(Node.java:721)<br>
at
javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2396)<br>
at
javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2542)<br>
at
com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:407)<br>
at
java.base/java.security.AccessController.doPrivileged(AccessController.java:400)<br>
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:406)<br>
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:436)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:575)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:352)<br>
at
com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)<br>
at
com.sun.glass.ui.win.WinApplication._enterNestedEventLoopImpl(Native
Method)<br>
at
com.sun.glass.ui.win.WinApplication._enterNestedEventLoop(WinApplication.java:211)<br>
at
com.sun.glass.ui.Application.enterNestedEventLoop(Application.java:515)<br>
at com.sun.glass.ui.EventLoop.enter(EventLoop.java:107)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.enterNestedEventLoop(QuantumToolkit.java:631)<br>
at
javafx.application.Platform.enterNestedEventLoop(Platform.java:301)<br>
at
hs.mediasystem.util.javafx.SceneUtil.enterNestedEventLoop(SceneUtil.java:75)
<-- just my wrapper to trace slow calls, it delegates to
Platform<br>
at
hs.mediasystem.runner.dialog.DialogPane.showDialog(DialogPane.java:78)<br>
at
hs.mediasystem.runner.dialog.Dialogs.showProgressDialog(Dialogs.java:163)<br>
at
hs.mediasystem.runner.dialog.Dialogs.runNested(Dialogs.java:103)<br>
at
hs.mediasystem.plugin.home.HomeScreenNodeFactory.lambda$1(HomeScreenNodeFactory.java:123) <br>
</p>
<p>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).<br>
</p>
<p> at
com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:360)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:80)<br>
at
javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102)<br>
at
javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:118)<br>
at
javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:172)<br>
at
javafx.scene.control.SelectionModel.setSelectedItem(SelectionModel.java:105)<br>
at
javafx.scene.control.MultipleSelectionModelBase.lambda$new$0(MultipleSelectionModelBase.java:67)<br>
at
com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:348)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.property.ReadOnlyIntegerPropertyBase.fireValueChangedEvent(ReadOnlyIntegerPropertyBase.java:78)<br>
at
javafx.beans.property.ReadOnlyIntegerWrapper.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:102)<br>
at
javafx.beans.property.IntegerPropertyBase.markInvalid(IntegerPropertyBase.java:114)<br>
at
javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:148)<br>
at
javafx.scene.control.SelectionModel.setSelectedIndex(SelectionModel.java:69)<br>
at
javafx.scene.control.MultipleSelectionModelBase.select(MultipleSelectionModelBase.java:424)<br>
at
javafx.scene.control.MultipleSelectionModelBase.select(MultipleSelectionModelBase.java:456)<br>
at
hs.mediasystem.plugin.home.HomeScreenNodeFactory.lambda$3(HomeScreenNodeFactory.java:176)
<-- triggers the creation of a ListView<br>
at
javafx.beans.value.ObservableValue.lambda$0(ObservableValue.java:364)<br>
at
com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)<br>
at
com.sun.javafx.binding.ConditionalBinding.conditionChanged(ConditionalBinding.java:53)<br>
at
com.sun.javafx.binding.Subscription.lambda$2(Subscription.java:63)<br>
at
com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)<br>
at
com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)<br>
at
com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)<br>
at
com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)<br>
at
com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)<br>
at
com.sun.javafx.binding.FlatMappedBinding.invalidateAll(FlatMappedBinding.java:102)<br>
at
com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)<br>
at
com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)<br>
at
com.sun.javafx.binding.FlatMappedBinding.invalidateAll(FlatMappedBinding.java:102)<br>
at
com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)<br>
at
com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:348)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:80)<br>
at
javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102)<br>
at
javafx.scene.Node$ReadOnlyObjectWrapperManualFire.fireSuperValueChangedEvent(Node.java:1053)
<-- there is a listener on a Scene property<br>
at javafx.scene.Node.invalidatedScenes(Node.java:1104)<br>
at javafx.scene.Node.setScenes(Node.java:1142)<br>
at javafx.scene.Parent.scenesChanged(Parent.java:772)
<-- the new Pane has a few nested Panes, so you see multiple
Parent assignments<br>
at javafx.scene.Node.invalidatedScenes(Node.java:1075)<br>
at javafx.scene.Node.setScenes(Node.java:1142)<br>
at javafx.scene.Parent.scenesChanged(Parent.java:772)<br>
at javafx.scene.Node.invalidatedScenes(Node.java:1075)<br>
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<br>
at javafx.scene.Parent$2.onChanged(Parent.java:372) <--
the new Pane gets its parent assigned (TransitionPane)<br>
at
com.sun.javafx.collections.TrackableObservableList.lambda$new$0(TrackableObservableList.java:45)<br>
at
com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)<br>
at
com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)<br>
at
javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:239)<br>
at
javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)<br>
at
javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)<br>
at
javafx.collections.ObservableListBase.endChange(ObservableListBase.java:211)<br>
at
javafx.collections.ModifiableObservableListBase.add(ModifiableObservableListBase.java:162)<br>
at
com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:319)<br>
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<br>
at
hs.mediasystem.util.javafx.ui.transition.TransitionPane.addAtStart(TransitionPane.java:105)<br>
at
hs.mediasystem.util.javafx.ui.transition.TransitionPane.add(TransitionPane.java:113)<br>
at
hs.mediasystem.runner.presentation.ViewPort.updateChildNode(ViewPort.java:68)
<-- here it installs the new Pane to display<br>
at
hs.mediasystem.runner.presentation.ViewPort.lambda$0(ViewPort.java:39)<br>
at
com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)<br>
at
com.sun.javafx.binding.Subscription.lambda$4(Subscription.java:83)<br>
at
com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)<br>
at
com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)<br>
at
javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:111)<br>
at
javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:118)<br>
at
javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:172)<br>
at
hs.mediasystem.presentation.ParentPresentation.navigateBack(ParentPresentation.java:39)
<-- this handles the keyPress (it was a "back" press)<br>
at hs.mediasystem.util.expose.Expose.lambda$3(Expose.java:55)<br>
at hs.mediasystem.util.expose.Trigger$1.run(Trigger.java:58)<br>
at
hs.mediasystem.runner.RootPresentationHandler.tryRunAction(RootPresentationHandler.java:106)<br>
at
hs.mediasystem.runner.RootPresentationHandler.handleActionEvent(RootPresentationHandler.java:81)<br>
at
com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:247)<br>
at
com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)<br>
at
com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)<br>
at
com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)<br>
at
com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)<br>
at
com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)<br>
at
hs.mediasystem.util.javafx.SceneUtil.lambda$1(SceneUtil.java:101)
<-- this is just my Slow Event detection, and just delegates
the event<br>
at
com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)<br>
at
com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)<br>
at
com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)<br>
at
com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)<br>
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)<br>
at
hs.mediasystem.util.javafx.base.Events.dispatchEvent(Events.java:35)<br>
at
hs.mediasystem.runner.action.InputActionHandler.handleKeyEvent(InputActionHandler.java:153)
<-- just code that handles a keypress that bubbled all the
way to the top<br>
at
hs.mediasystem.runner.action.InputActionHandler.onKeyPressed(InputActionHandler.java:138)<br>
at
com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)<br>
at
com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)<br>
at
com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)<br>
at
com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)<br>
at
com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)<br>
at
hs.mediasystem.util.javafx.SceneUtil.lambda$1(SceneUtil.java:101)
<-- this is just my Slow Event detection, and just delegates
the event<br>
at
com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)<br>
at
com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)<br>
at
com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)<br>
at
com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)<br>
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)<br>
at javafx.event.Event.fireEvent(Event.java:198)<br>
at javafx.scene.Scene$KeyHandler.process(Scene.java:4113)<br>
at javafx.scene.Scene.processKeyEvent(Scene.java:2159)<br>
at
javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2627)<br>
at
com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:218)<br>
at
com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:150)<br>
at
java.base/java.security.AccessController.doPrivileged(AccessController.java:400)<br>
at
com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$1(GlassViewEventHandler.java:250)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)<br>
at
com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:249)<br>
at com.sun.glass.ui.View.handleKeyEvent(View.java:542)<br>
at com.sun.glass.ui.View.notifyKey(View.java:966)<br>
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)<br>
at
com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)<br>
at java.base/java.lang.Thread.run(Thread.java:1583)<br>
</p>
<p>And the NPE exception:</p>
<p>java.lang.NullPointerException: Cannot invoke
"javafx.scene.Node.getScene()" because "<local2>" is null<br>
at
javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2395)<br>
at
javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2542)<br>
at
com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:407)<br>
at
java.base/java.security.AccessController.doPrivileged(AccessController.java:400)<br>
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:406)<br>
(rest of trace identical to the AssertionError one)</p>
<p>And another variant of the NPE exception:<br>
<br>
java.lang.NullPointerException: Cannot invoke
"javafx.scene.Node.getScene()" because "<local2>" is null<br>
at
javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2395)<br>
at
javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2542)<br>
at
com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:407)<br>
at
java.base/java.security.AccessController.doPrivileged(AccessController.java:400)<br>
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:406)<br>
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:436)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:575)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)<br>
at
com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:352)<br>
at
com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)<br>
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)<br>
at
com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)<br>
at java.base/java.lang.Thread.run(Thread.java:1583)<br>
</p>
<p><br>
</p>
<p><br>
</p>
<p><br>
</p>
<p><br>
</p>
</blockquote>
</body>
</html>