<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Small sample program that triggers an assert in Parent:</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:#646464;">@</span><span style="color:#646464;">Override</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">void</span><span
style="color:#000000;"> start(Stage primaryStage) {</span></p><p
style="margin:0;"><span style="color:#000000;"> HBox hbox = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> HBox();</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> Scene scene = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> Scene(hbox);</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> Button button1 = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> Button(</span><span style="color:#2a00ff;">"Hi"</span><span
style="color:#000000;">);</span></p><p style="margin:0;"><span
style="color:#000000;"> Button button2 = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> Button(</span><span style="color:#2a00ff;">"World"</span><span
style="color:#000000;">);</span></p><p style="margin:0;"><span
style="color:#000000;"> Button button3 = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> Button(</span><span style="color:#2a00ff;">"!"</span><span
style="color:#000000;">);</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> hbox.getChildren().addAll(button1, button2);</span></p><p
style="margin:0;"><span style="color:#000000;"> hbox.getChildren().addListener(</span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> InvalidationListener() {</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#646464;">@Override</span></p><p style="margin:0;"><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">void</span><span
style="color:#000000;"> invalidated(Observable observable) {</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">if</span><span
style="color:#000000;">(!hbox.getChildren().contains(button3)) {</span></p><p
style="margin:0;"><span style="color:#000000;"> hbox.getChildren().addAll(button3, button3);</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;"><span style="color:#000000;"> });</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> button1.toFront();</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> primaryStage.setScene(scene);</span></p><p
style="margin:0;"><span style="color:#000000;"> primaryStage.show();</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p></div>
</div>
<p></p>
<p>--John<br>
</p>
<div class="moz-cite-prefix">On 22/08/2024 09:37, John Hendrikx
wrote:<br>
</div>
<blockquote type="cite"
cite="mid:a255c77d-3bac-48c2-ae4e-20b9053812ad@gmail.com">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<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>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>
</blockquote>
</body>
</html>