EasternEdgeRobotics / Software_2017

The control software for 2017
MIT License
3 stars 0 forks source link

Null pointer exception on the JavaFX thread. #309

Closed cal-pratt closed 7 years ago

cal-pratt commented 7 years ago

I've managed to pin point a null pointer exception that pops up on the topsides sometimes. Code to reproduce this exception:

        final Observable<HeartbeatValue> rasprimeHeartbeats = eventPublisher
            .valuesOfType(RasprimeHeartbeatValue.class).cast(HeartbeatValue.class);
        final Observable<HeartbeatValue> picameraAHeartbeats = eventPublisher
            .valuesOfType(PicameraAHeartbeatValue.class).cast(HeartbeatValue.class);
        final Observable<HeartbeatValue> picameraBHeartbeats = eventPublisher
            .valuesOfType(PicameraBHeartbeatValue.class).cast(HeartbeatValue.class);

        subscriptions.addAll(
            setIndicator(rasprimeHeartbeats, view.rasprimeIndicator),
            setIndicator(picameraAHeartbeats, view.picameraAIndicator),
            setIndicator(picameraBHeartbeats, view.picameraBIndicator));
...

    private Subscription setIndicator(final Observable<HeartbeatValue> heartbeats, final ToggleButton indicator) {
        final Observable<Boolean> timeout = Observable.just(false)
            .delay(maxHeartbeatGap, TimeUnit.SECONDS, JAVA_FX_SCHEDULER)
            .concatWith(Observable.never())
            .takeUntil(heartbeats)
            .repeat();
        return new CompositeSubscription(
            timeout.observeOn(JAVA_FX_SCHEDULER).subscribe(h -> indicator.setBackground(MainView.LOST_BG)),
            heartbeats.observeOn(JAVA_FX_SCHEDULER).subscribe(h -> indicator.setBackground(MainView.FOUND_BG)));
    }

I can then cause the exception by emitting a lot of heartbeats on a different computer:

for (int i = 0; i < 10000000; i++) {
      Thread.sleep(10);
      System.out.println(i);
      eventPublisher.emit(new RasprimeHeartbeatValue());
      eventPublisher.emit(new PicameraAHeartbeatValue());
      eventPublisher.emit(new PicameraBHeartbeatValue());
}

This causes the exception:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
        at com.sun.scenario.animation.AbstractMasterTimer.timePulseImpl(AbstractMasterTimer.java:344)
        at com.sun.scenario.animation.AbstractMasterTimer$MainLoop.run(AbstractMasterTimer.java:267)
        at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:506)
        at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
        at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$346(QuantumToolkit.java:319)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
        at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
        at com.sun.glass.ui.gtk.GtkApplication.lambda$null$450(GtkApplication.java:139)
        at java.lang.Thread.run(Thread.java:745)

Now, I have no idea why this would be causing the NPE as the operation is performed on the JavaFX thread seeing as each action happen on observeOn(JAVA_FX_SCHEDULER):

timeout.observeOn(JAVA_FX_SCHEDULER).subscribe(h -> indicator.setBackground(MainView.LOST_BG));
heartbeats.observeOn(JAVA_FX_SCHEDULER).subscribe(h -> indicator.setBackground(MainView.FOUND_BG));

Maybe the speed of updating the FX components can cause issues? But that's also strange because I can update the imageview at fairly fast rates. Will investigate more

cal-pratt commented 7 years ago

Okay I solved it, but the answer still makes me scratch my head. Apparently the JavaFX thread is not to happy with the way the timeout observable is constructed:

final Observable<Boolean> timeout = Observable.just(false)
    .delay(maxHeartbeatGap, TimeUnit.SECONDS, JAVA_FX_SCHEDULER)
    .concatWith(Observable.never())
    .takeUntil(heartbeats)
    .repeat();

This observable alone seems to cause the issue, even if its only subscribed by a System.out.println call. I have no idea what funkiness is going on inside the JavaFX scheduler , but replacing it with a Schedulers.newThread() makes this problem go away. I think this might be a bug within the JavaFX code itself; works on one scheduler it should be fine on them all?

I don't have time to figure this out fully, but its worth remembering for the future. PR to follow shortly