Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8092492

Service can be GC'ed before it fulfills its contract

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Not an Issue
    • Icon: P3 P3
    • None
    • 8u25
    • javafx
    • Win 7

      If you have a listener on a Service property but don't explicitly hold a reference to it, the Service can be garbage collected, and notifications lost, before the Task completes.

      In the code below, there is a listener on Service's State property. If the class MyTask is static, the notifications of RUNNING and SUCCEEDED are never delivered.
      If MyTask is made a normal inner class (and thus it holds a reference to the Service), then the RUNNING and SUCCEEDED notifications are delivered.

      When there is a listener on the State property, all state transitions should be delivered.

      What is happening is that Service attaches the listener using a Weak Reference, so the listener doesn't hold the Service in memory. Under memory pressure the Service gets GC'ed & stops delivering notifications even though the underlying Task succeeds.

      This was a really hard bug to track down.

      import javafx.application.Application;
      import javafx.concurrent.Service;
      import javafx.concurrent.Task;
      import javafx.scene.Scene;
      import javafx.scene.control.Label;
      import javafx.scene.layout.StackPane;
      import javafx.stage.Stage;

      import java.util.ArrayList;

      public class ServiceGCTest extends Application
      {
        @Override
        public void start(Stage primaryStage) throws Exception
        {
          startService();

          Label lab = new Label("Hello");
          StackPane root = new StackPane();
          root.getChildren().add(lab);
          primaryStage.setScene(new Scene(root, 200, 250));
          primaryStage.show();

          ArrayList<String> al = new ArrayList<>(); // apply pressure to the GC
          for (int i = 0; i < 100_000; i++)
            al.add("A really long long long really long long long really long long long really long long long String " + System.currentTimeMillis());
          Thread.sleep(1_000);
        }

        private void startService()
        {
          Service s = new MyService();
          s.stateProperty().addListener((observable, oldValue, newValue) -> System.out.println("new value " + newValue));

          s.start();
        }

        public static class MyService extends Service
        {
          @Override
          protected Task createTask()
          {
            return new MyTask();
          }

          static class MyTask extends Task<Void> // if static is removed, the bug goes away!
          {
            @Override
            protected Void call() throws Exception
            {
              return null;
            }
          }
        }
      }

            kcr Kevin Rushforth
            ngalarneajfx Neil Galarneau (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported: