JDK-8097469 : Better error checking and docs if Printing or Dialogs called from Animation
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: 8u20
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2014-12-16
  • Updated: 2016-06-27
  • Resolved: 2015-06-04
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
8u60Fixed 9Fixed
Related Reports
Relates :  
Relates :  
Description
As of 8u20, calling printPage() from inside an AnimationTimer callback no longer works as it did prior to that. Attempting it results in the following exception:

Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Key not associated with a running event loop: java.lang.Object@2b4b3670

Here is a sample program illustrating the issue:

public class PrintBugTest extends Application {
    @Override
    public void start(Stage primaryStage) {
        ComboBox<Printer> printerComboBox = new ComboBox<>();
        printerComboBox.getItems().addAll(Printer.getAllPrinters());
        printerComboBox.getSelectionModel().select(Printer.getDefaultPrinter());
        Button btn = new Button("Print");
        
        btn.setOnAction(e -> {
             new AnimationTimer() {
            @Override
                public void handle(long l) {
                    stop();
                    Node node = new Circle(100, 200, 200);
                    PrinterJob job = PrinterJob.createPrinterJob();
                    job.setPrinter(printerComboBox.getValue());
                    job.printPage(node);
                    job.endJob();
                }
            }.start();
            
        });
        VBox root = new VBox();
        root.setSpacing(20);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10,10,10,10));
        root.getChildren().addAll(new HBox(new Label("Select Printer: "), printerComboBox), btn);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}
Comments
http://hg.openjdk.java.net/openjfx/8u-dev/rt/rev/bea638dd0dbb
04-06-2015

Jonathan and I talked offline and he is OK with the current patch. I said I would add a note about needing to be on the FX app thread to the javadoc for the Dialog.showAndWait() method, which now reads as follows: * This method must be called on the JavaFX Application thread. * Additionally, it must either be called from an input event handler or * from the run method of a Runnable passed to everything else stays unchanged from webrev.02. Here is webrev.03 for completeness: http://cr.openjdk.java.net/~kcr/RT-39689/webrev.03/
04-06-2015

I can add the additional clarifying comments to the javadoc. However, I don't agree that the exception message should reference the FX application thread. In this case, it isn't a question of being called from the FX application thread or not -- both the good case (input event handler or runLater) and the bad case (animation or layout processing) are called on the FX application thread. There is already (for showAndWait not for printing) a separate check and a separate message if called from a different thread than the FX application thread, meaning that the exception in question will never be thrown unless called from the FX application thread.
01-06-2015

Sorry to be finicky, but I still think we can improve clarity here, primarily by referencing the JavaFX Application Thread, which I think is the missing piece of detail. I would have the following text: "This method must be called from the JavaFX Application Thread (either directly, from within an input event handler, or placed on this thread by passing a Runnable to Platform.runLater). It must not be called during animation or layout processing." I would also improve the exception message to: "showAndWait is not allowed during animation or layout processing - it must be called from the JavaFX Application Thread."
01-06-2015

Finally getting back to this... I decided to redo the error message and javadoc to make it more clear (I hope). - added the following text to the description for each affected method: This method must either be called from an input event handler or from the run method of a Runnable passed to Platform.runLater. It must not be called during animation or layout processing. - changed the @throws doc to: @throws IllegalStateException if this method is called during animation or layout processing. - changed the exception message itself to: new IllegalStateException("showAndWait is not allowed during animation or layout processing"); New webrev: http://cr.openjdk.java.net/~kcr/RT-39689/webrev.02/
29-05-2015

Code looks fine. I think the discussion is just about the docs. 314 * @throws IllegalStateException if this method is called outside of 315 * event handler. This includes animation callbacks, properties ^^ "AN" > The problem is that I don't know what I need to do to not get this exception. Perhaps some of the words in the javadoc can be added to the exception message. As in throw new IllegalStateException("showAndWait is only allowed only while handling system events, and not (for example) timer events.") But I don't need to see an updated webrev for whatever is decided upon, so "+1"
18-05-2015

Conceptually it is fine, but I find the IllegalStateException message slightly difficult to parse: "showAndWait is only allowed only while handling system events", for two reasons: 1) double use of 'only' 2) Even for me, I don't exactly understand the context of what you mean by 'handling system events'. The problem is that I don't know what I need to do to not get this exception.
17-05-2015

Updated webrev: http://cr.openjdk.java.net/~kcr/RT-39689/webrev.01/ * Fixed a problem in QuantumToolkit where inPulse could be decremented even if not incremented. * I added the same checks to Dialog that are in Stage to throw the exception early (so the DIALOG_SHOWING and DIALOG_SHOWN events won't be delivered in that case since the dialog won't be shown). * Updated the unit test to also test an Alert dialog.
16-05-2015

Phil: If you haven't started reviewing it yet, I discovered a bug in #1 (by inspection). I will upload a new version shortly.
15-05-2015

Webrev: http://cr.openjdk.java.net/~kcr/RT-39689/webrev.00/ Fix is as follows: 1) The check for whether we are in a pulse has been modified to handle the case of a reentrant call 2) showAndWait now checks whether nestedEventLoops are permitted before showing the window 3) javadoc was added to the three methods in PrinterJob documenting the ISE in case it is called outside the event loop 4) The three print methods now check whether nestedEventLoops are permitted (if we are on the FX app thread) before attempting to show a printer dialog I added new unit tests for this.
15-05-2015

While testing my fix, I found that the current method of determining whether we are in a pulse is insufficient. We use a boolean variable to track whether we are in a pulse, but in some cases the pulse method can be called reentrantly (e.g., for live resize), so we need to change this to use a counter or it will not properly prevent nested loops in those cases. In light of this failure, I am raising the priority of this to P3 since the problem is more serious than just better reporting of the error condition.
15-05-2015

This was introduced in 8u20 by the fix for RT-36256, which was an intentional change to prohibit showAndWait() and other uses of the nested event loop from running outside an event handler. The reason for this is that running a nested event loop, which is needed for showing blocking dialogs (such as print dialogs, but not limited to them), from an animation callback or any other method that runs during the pulse has never worked correctly, since it would block the pulse from running (meaning that a native dialog such as is used in printing might work, but the rest of your application would stop rendering). Starting with 8u20 we throw an exception to prevent this case from happening. This is documented in Stage.showAndWait (but not in other places where we use a blocking dialog). However, it looks like there are a couple of issues with the fix. First, we don't prevent the dialog from being shown in the showAndWait() method, which leads to a second illegal state exception when the dialog is closed. Second, it appears that the printing code has a similar issue to showAndWait -- somewhere in the printing code it appears to be catching the initial exception, and is subsequently calling the code to exit a nested event loop that was never started, which produces exception you are seeing. Our fix for this bug will be to tighten up the spec and deal with the error conditions more gracefully, which we will do for 8u60. The upshot of this is that you will need to change your application code to not call printing from an animation timer. You can do this either by using another mechanism to trigger the printing, or if you really do want to trigger it from an animation timer, then you will need to wrap your calls to printing in a Platform.runLater().
19-12-2014

I can reproduce it. I don't think this is an animation bug. I would guess that animation is a victim here, and that it's a Graphics bug of some sort: either in Quantum or in the Printing dialog code. I'll do an initial evaluation.
17-12-2014