You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌套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);
}

问题原因分析

  1. 串行嵌套逻辑导致超时无法触发:当前代码中,只有当当前scope的joinUntil成功返回(即内部Thread.sleep(5秒)完成)后,才会调用下一层嵌套的nestedTest_pureJava。外层设置的8秒超时远大于5秒任务时长,所以joinUntil永远不会抛出TimeoutException,自然不会执行shutdown逻辑。
  2. 超时时间计算错误:Instant.now().plusSeconds(untilTimeout.getSeconds())仅处理秒单位,若untilTimeout使用其他时间单位(如分钟)会导致计算错误,正确写法应为Instant.now().plus(untilTimeout)。
  3. 上层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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 18:17:33