JDK-8136535 : JavaFX NumberAxis AutoRange Infinite Loop
  • Type: Bug
  • Component: javafx
  • Sub-Component: controls
  • Affected Version: 8u40
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_8
  • CPU: x86
  • Submitted: 2015-09-11
  • Updated: 2020-01-31
  • Resolved: 2015-11-23
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u92Fixed 9Fixed
Description
FULL PRODUCT VERSION :
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 10.0.10240]

A DESCRIPTION OF THE PROBLEM :
The NumberAxis.autoRange method may enter an infinite loop if consecutive double values are added to a chart that are very close together. This is noticeable because the JavaFX thread will hang due to the infinite loop.

The chart axis must have auto ranging enabled and NumberAxis.setForceZeroInRange(false).

See attached minimal, complete, and verifiable example to repro.

See http://stackoverflow.com/questions/32513409/javafx-numberaxis-autorange-infinite-loop

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create LineChart
2. Create Y-Axis NumberAxis with auto range enable and setForceZeroInRange(false)
3. Add -20.98295609742171 to Y-Axis
4. Add -20.982956097421706 to Y-Axis

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The JavaFX thread should not hang and the chart should continue to update.
ACTUAL -
The JavaFX thread hangs and the chart stops updating

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.*;

import javafx.application.*;
import javafx.concurrent.*;
import javafx.scene.*;
import javafx.scene.chart.*;
import javafx.stage.*;
import javafx.util.*;

public class SmallTickUnitTest extends Application {

  @Override
  public void start(Stage stage) {
    final NumberAxis xAxis = new NumberAxis();
    final NumberAxis yAxis = new NumberAxis();
    final LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);

    xAxis.setLabel("Time");
    yAxis.setLabel("Data");
    lineChart.setTitle("Measured Data");

    XYChart.Series<Number, Number> series1 = new XYChart.Series<>();
    series1.setName("Wind Speed");
    lineChart.getData().addAll(series1);
    
    // If this is set to true then the bug will not occur
    yAxis.setForceZeroInRange(false);

    // Use two values that are extremely close together
    List<Double> values = Arrays.asList(-20.98295609742171, -20.982956097421706);
    final Iterator<Double> itr = values.iterator();
    
    ScheduledService<XYChart.Data<Number, Number>> svc = 
       new ScheduledService<XYChart.Data<Number, Number>>() {

      int time = 0;

      @Override
      protected Task<XYChart.Data<Number, Number>> createTask() {
        return new Task<XYChart.Data<Number, Number>>() {
          @Override
          protected XYChart.Data<Number, Number> call() {
            time++;
            return new XYChart.Data<>(time, itr.hasNext() ? itr.next() : 0);
          }
        };
      }
    };
    
    svc.setPeriod(Duration.seconds(2));
    svc.setRestartOnFailure(true);
    svc.setOnSucceeded((WorkerStateEvent e) -> {
      XYChart.Data<Number, Number> value = svc.getLastValue();
      if (value != null) {
        series1.getData().add(value);
      }
    });

    Scene scene = new Scene(lineChart, 800, 600);
    stage.setTitle("Line Chart Sample");
    stage.setScene(scene);
    stage.show();
    svc.start();
  }

  public static void main(String[] args) {
    launch(args);
  }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Sanitize floating point chart input data such that values cannot be close to each other.


Comments
Thanks. Approved for backport to 8u-dev.
23-11-2015

I woudn't say it's 100% safe, the behavior could be slightly different due to the floating point arithmetic differences. I tested it quite extensively and haven't found any regressions.
23-11-2015

Since this is a regression introduced in an 8 update release, we should consider a backport to 8u-dev. How safe do you think the fix is?
23-11-2015

Changeset: b45ddd59073b Author: vadim Date: 2015-11-23 13:48 +0300 URL: http://hg.openjdk.java.net/openjfx/9-dev/rt/rev/b45ddd59073b
23-11-2015

+1
22-11-2015

Jonathan, Please review the fix: http://cr.openjdk.java.net/~vadim/8136535/webrev.00/ The reason for the bug is that the calculated value of tickUnit (and minorTickUnit) is less than machine epsilon and loops which are iterating over the range using only double values get stuck. The loop in the autoRange method can be fixed by treating small range as zero range, so it will be expanded. Expanding zero range also happens to be not quite correct, in case of very large numbers we would get the same infinite loop. So instead of using hardcoded range of 2 it now expands the range by 2% of the value. Not autoranged axes also could suffer from this so I added counters to loops as well.
20-11-2015

I can confirm this bug. I also confirm that it works with 8u31, and not with later releases, which suggests that the regression was introduced in 8u40.
15-09-2015