嵌套StructuredTaskScope中joinUntil失效问题排查
嵌套StructuredTaskScope.ShutdownOnFailure时joinUntil超时不生效问题排查
环境
- Windows 11
- Java 22(启用预览特性)
问题概述
使用JEP 453引入的joinUntil功能限制任务执行时长,但嵌套使用StructuredTaskScope.ShutdownOnFailure时,执行未按预期停止。即使捕获TimeoutException并调用scope.shutdown(),问题仍未解决。
初始代码
import java.time.Duration; import java.time.Instant; import java.util.concurrent.ExecutionException; import java.util.concurrent.StructuredTaskScope; public class Main { private static final System.Logger out = System.getLogger(Main.class.getName()); public static void main(String... s) { nestedTest_pureJava( Duration.ofSeconds(8), Duration.ofSeconds(5), 5 ); } private static void nestedTest_pureJava(Duration untilTimeout, Duration workLoad, int nestedId) { if (nestedId < 0) { out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> skip -> " + nestedId); return; } out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> begin -> " + nestedId); try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { scope.fork(() -> { Thread.sleep(workLoad); return null; }); scope.joinUntil(Instant.now().plusSeconds(untilTimeout.getSeconds())); scope.throwIfFailed(); nestedTest_pureJava(untilTimeout, workLoad, nestedId - 1); } catch (InterruptedException | TimeoutException | ExecutionException e) { throw new RuntimeException(e); } out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> end -> " + nestedId); } }
执行输出
nestedTest_pureJava -> begin -> 5 nestedTest_pureJava -> begin -> 4 nestedTest_pureJava -> begin -> 3 nestedTest_pureJava -> begin -> 2 nestedTest_pureJava -> begin -> 1 nestedTest_pureJava -> begin -> 0 nestedTest_pureJava -> skip -> -1 nestedTest_pureJava -> end -> 0 nestedTest_pureJava -> end -> 1 nestedTest_pureJava -> end -> 2 nestedTest_pureJava -> end -> 3 nestedTest_pureJava -> end -> 4 nestedTest_pureJava -> end -> 5
修改后代码(添加shutdown仍无效)
private static void nestedTest_pureJava(Duration untilTimeout, Duration workLoad, int nestedId) { if (nestedId < 0) { out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> skip -> " + nestedId); return; } out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> begin -> " + nestedId); var scope = new StructuredTaskScope.ShutdownOnFailure(); try { scope.fork(() -> { Thread.sleep(workLoad); return null; }); scope.joinUntil(Instant.now().plusSeconds(untilTimeout.getSeconds())); scope.throwIfFailed(); nestedTest_pureJava(untilTimeout, workLoad, nestedId - 1); } catch (InterruptedException | TimeoutException | ExecutionException e) { if (e instanceof TimeoutException) { scope.shutdown(); } throw new RuntimeException(e); } finally { scope.close(); } out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> end -> " + nestedId); }
问题原因分析
- 串行嵌套逻辑导致超时无法触发:当前代码中,只有当当前scope的
joinUntil成功返回(即内部Thread.sleep(5秒)完成)后,才会调用下一层嵌套的nestedTest_pureJava。外层设置的8秒超时远大于5秒任务时长,所以joinUntil永远不会抛出TimeoutException,自然不会执行shutdown逻辑。 - 超时时间计算错误:
Instant.now().plusSeconds(untilTimeout.getSeconds())仅处理秒单位,若untilTimeout使用其他时间单位(如分钟)会导致计算错误,正确写法应为Instant.now().plus(untilTimeout)。 - 上层scope无法控制嵌套scope:即使外层触发超时,由于嵌套调用是在当前scope任务完成后才执行,此时上层scope的
shutdown无法影响已经启动的嵌套scope。
解决方案
要实现嵌套scope的超时控制,需将嵌套任务纳入当前scope的管理,或让上层的取消信号传递到下层。以下是修正后的代码:
import java.time.Duration; import java.time.Instant; import java.util.concurrent.ExecutionException; import java.util.concurrent.StructuredTaskScope; public class Main { private static final System.Logger out = System.getLogger(Main.class.getName()); public static void main(String... s) { try { nestedTest_pureJava( Duration.ofSeconds(3), // 缩短超时时间,确保能触发超时 Duration.ofSeconds(5), 5 ); } catch (RuntimeException e) { out.log(System.Logger.Level.INFO, "Main caught exception: " + e.getMessage()); } } private static void nestedTest_pureJava(Duration untilTimeout, Duration workLoad, int nestedId) { if (nestedId < 0) { out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> skip -> " + nestedId); return; } out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> begin -> " + nestedId); var deadline = Instant.now().plus(untilTimeout); try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { // 将当前任务和嵌套任务都fork到当前scope scope.fork(() -> { Thread.sleep(workLoad); return null; }); // 嵌套任务也作为子任务fork,而非串行调用 scope.fork(() -> { nestedTest_pureJava(untilTimeout, workLoad, nestedId - 1); return null; }); // 使用正确的超时时间点 scope.joinUntil(deadline); scope.throwIfFailed(); } catch (InterruptedException | TimeoutException | ExecutionException e) { // ShutdownOnFailure会自动终止所有子任务,无需手动调用shutdown throw new RuntimeException("Scope timed out or failed: " + nestedId, e); } out.log(System.Logger.Level.INFO, "nestedTest_pureJava -> end -> " + nestedId); } }
关键修改点
- 并行化嵌套任务:将嵌套调用作为子任务
fork到当前scope,而非串行执行,这样上层scope的超时会终止所有子任务(包括嵌套的scope)。 - 修正超时时间计算:使用
Instant.now().plus(untilTimeout)替代plusSeconds,支持任意时间单位。 - 利用ShutdownOnFailure特性:
ShutdownOnFailure在发生失败或超时会自动终止所有子任务,无需手动调用shutdown。
执行预期
当设置的超时时间(如3秒)小于任务时长(5秒)时,外层scope会触发TimeoutException,并终止所有嵌套的子任务,输出会显示中途终止的信息,不会执行到所有end语句。
内容的提问来源于stack exchange,提问作者Tugalsan Karabacak
相关产品推荐
相关产品推荐

