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

Structured Concurrency: IllegalStateException on Subtask.get() after timeout

XMLWordPrintable

      ADDITIONAL SYSTEM INFORMATION :
      Eclipse Temurin 25

      A DESCRIPTION OF THE PROBLEM :
      I encountered an issue with Structured Concurrency (JEP 505, Fifth Preview) in Java 25.

      When a StructuredTaskScope is configured with a timeout factory (cf.withTimeout(...)) and the scope times out, scope.join() correctly throws a StructuredTaskScope.TimeoutException. However, after catching this exception, attempting to retrieve the result of a successfully completed Subtask via subtask.get() unexpectedly throws an IllegalStateException: join not called.

      The expected behavior is that even after a scope timeout, the results of subtasks that completed successfully before the deadline should be retrievable. Subtasks that did not complete should be in the UNAVAILABLE state.

      I am also aware of the issue JDK-8367858, which is targeted for the Sixth Preview. However, I am unable to determine if the issue I am reporting is related to it or if it has already been resolved by that fix, as I am reporting this based on the Fifth Preview.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Compile and run the provided Java code (javac --release 25 --enable-preview Main.java).
      2. The program will print that it has caught a TimeoutException.
      3. It will then throw an IllegalStateException when checking the results of the subtasks.

      package org.example;

      import java.time.Duration;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.concurrent.StructuredTaskScope;

      public class Main {
          public static void main(String[] args) throws InterruptedException {

              try (var scope = StructuredTaskScope.open(
                      StructuredTaskScope.Joiner.<String>awaitAll(),
                      cf -> cf.withTimeout(Duration.ofSeconds(5)))) {
                  List<StructuredTaskScope.Subtask<String>> tasks = new ArrayList<>();
                  for (int i = 0; i <= 10; i++) {
                      final int a = i;
                      final StructuredTaskScope.Subtask<String> t = scope.fork(() -> {
                          // Use a longer sleep duration to ensure timeout
                          Thread.sleep(Duration.ofSeconds(a));
                          return "task" + a;
                      });
                      tasks.add(t);
                  }
                  try {
                      System.out.println("begin scope.join()");
                      scope.join();
                  } catch (StructuredTaskScope.TimeoutException e) {
                      System.out.println("caught TimeoutException");
                  }
                  System.out.println("check results of subtasks");
                  for (StructuredTaskScope.Subtask<String> t: tasks) {
                      switch (t.state()) {
                          case StructuredTaskScope.Subtask.State.SUCCESS:
                              // This line throws the exception
                              final String s = t.get();
                              System.out.println("success: " + s);
                              break;
                          case StructuredTaskScope.Subtask.State.FAILED:
                              System.out.println("failed: " + t.exception().getMessage());
                              break;
                          case StructuredTaskScope.Subtask.State.UNAVAILABLE:
                              System.out.println("unavailable");
                              break;
                      }
                  }
              }
          }
      }


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Example of expected output:

      begin scope.join()
      caught TimeoutException
      check results of subtasks
      success: task0
      success: task1
      success: task2
      success: task3
      success: task4
      unavailable
      unavailable
      unavailable
      unavailable
      unavailable
      unavailable

      The program should not throw an IllegalStateException. It should print the state and result of each subtask. Subtasks that finished within the 5-second timeout should be in the SUCCESS state, and their results should be printed. Subtasks that did not finish should be in the UNAVAILABLE state.
      ACTUAL -
      begin scope.join()
      caught TimeoutException
      check results of subtasks
      Exception in thread "main" java.lang.IllegalStateException: join not called
      at java.base/java.util.concurrent.StructuredTaskScopeImpl.ensureJoinedIfOwner(StructuredTaskScopeImpl.java:127)
      at java.base/java.util.concurrent.StructuredTaskScopeImpl$SubtaskImpl.get(StructuredTaskScopeImpl.java:358)
      at org.example.Main.main(Main.java:33)


      The program throws java.lang.IllegalStateException: join not called when t.get() is invoked on a subtask that should have completed successfully (e.g., the task with a 0-second sleep).

      Additional Notes
      As a test, I tried adding another scope.join() call inside the catch (StructuredTaskScope.TimeoutException e) block. This resulted in a different exception: java.lang.IllegalStateException: Already joined or scope is closed. This indicates that the scope is indeed considered "joined" after the timeout, which makes the original "join not called" exception seem contradictory and likely a bug.

      // ...
      } catch (StructuredTaskScope.TimeoutException e) {
          System.out.println("caught TimeoutException");
          scope.join(); // Adding this line
      }
      //...

      Output with the extra join() call:

      begin scope.join()
      caught TimeoutException
      Exception in thread "main" java.lang.IllegalStateException: Already joined or scope is closed
      at java.base/java.util.concurrent.StructuredTaskScopeImpl.ensureNotJoined(StructuredTaskScopeImpl.java:117)
      at java.base/java.util.concurrent.StructuredTaskScopeImpl.join(StructuredTaskScopeImpl.java:237)
      at org.example.Main.main(Main.java:28)



            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: