如何在Java代理程序中停止/暂停主程序/线程

3
我有一个Gradle测试任务,从给定的文件运行一系列测试。 有时,某个特定的测试执行会被卡住,无法继续执行列表中的下一个测试。
为此,我尝试添加一个Java代理来检测每个测试执行中的超时,并在这种情况下调用System.exit()。(我知道调用System.exit()似乎是一个仓促的决定,但是抛出异常似乎不能停止测试执行) 该Java代理使用Byte-Buddy建议来完成这项工作。
public class TimerAdvice {
    public static CountDownLatch latch;

    @Advice.OnMethodEnter
    static long enter(@Advice.This Object thisObject,
                      @Advice.Origin String origin,
                      @Advice.Origin("#t #m") String detaildOrigin) throws InterruptedException {
        System.out.println("Timer Advice Enter thread: " + Thread.currentThread().getName() + " time: " + Instant.now().toString());

        latch = new CountDownLatch(1);

        ThreadFactory factory = new MyThreadFactory(new MyExceptionHandler());
        ExecutorService threadPool = Executors.newFixedThreadPool(1, factory);
        threadPool.execute(new TestCallable());

        return System.nanoTime();
    }

    @Advice.OnMethodExit (onThrowable = Throwable.class)
    static void onExit(@Advice.Origin Method method) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        System.out.println("Timer Advice Exit thread: " + Thread.currentThread().getName() + " time: " + Instant.now().toString());
        System.out.println("Counting down");
        latch.countDown();
    }
}

这将生成一个后台线程,它将等待latch被倒计时。

public class TestCallable implements Runnable {
    @Override
    public void run()  {
        try {
            latch.await(10, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
            throw new IllegalStateException(e.getMessage());
        }
        if(latch.getCount() > 0) {
            System.err.println("Callable thread"
                    + Thread.currentThread().getName() +
                    "TIMEOUT OCCURED!!!!");
            System.exit(1);
        }
    }
}

latch countDown() 方法将会被处理 OnExit advice 的方法调用。在此之前,线程会等待指定的超时时间。

我的问题是,为什么 System.exit() 调用不影响测试执行/JVM?当这个线程调用 System.exit() 时,测试线程仍然继续执行,就好像什么都没有发生一样。我想在这个时候停止测试的执行。

有什么建议可以在检测到超时时如何停止整个测试执行过程吗?


难道在单独的进程中运行有问题的测试,并使用Process.destroy在运行时间过长时终止它,不是更简单吗?修复导致测试卡住的问题不是更好吗? - Stephen C
抱歉,但是每个测试框架都有自己的超时机制,通常通过注释应用,也可以通过全局配置或编程方式应用。感觉你在重新发明轮子。你没有解释你探索了哪些更加规范的选项。至于 System.exit(),它似乎没有任何意义,因为通常你会在单个 VM 中执行所有测试模块或至少大组的测试。对于 17k 个测试,你不会启动 17k 个 JVM,对吧? - kriegaex
@kriegaex 哈哈哈不是的。这17k个测试被分成50-100个测试的批次。这些批次排队到一组节点。每个节点负责执行其分配的“批次”。现在,我想为每个测试引入一个超时。它可能是Junit3/Junit4。最终目标是,批次中一个测试的超时不应该停止节点中剩余测试集(在同一批次中)的执行。 - Abhijith Gururaj
JUnit 3 - 呃!我通常使用Spock编写我的测试,但也了解一些JUnit 4/5。通常我使用Maven,从不使用Gradle。但是如果您在GitHub上发布一个MCVE来重现问题,我可以研究您的问题。只需创建一个微小但完整的示例项目,其中包含2-3个虚拟的JUnit 3 + 4测试和您的Byte Buddy设置。顺便说一句,对于您来说,AspectJ而不是BB是否是一种替代方案?我比BB更了解AspectJ,并建议将其用于此目的。但是,BB解决方案肯定也是可行的。 - kriegaex
顺便说一句,如果您知道如何使用正则表达式搜索和替换,甚至了解如何使用IntelliJ IDEA的结构化搜索和替换等高级功能,将JUnit 3测试和套件迁移到JUnit 4应该非常简单。如果您想了解实际上有多简单,请参见此答案。 (https://dev59.com/c3VC5IYBdhLWcg3wihmv#677356) - kriegaex
显示剩余9条评论
1个回答

1

原帖作者表示我的有关安全管理器的评论帮助他找到了根本原因,因此我正在将其转换为答案:

如文档所述,System.exit()如果有安全管理器阻止其关闭JVM,则不会关闭JVM。在这种情况下,您应该看到SecurityException

Gradle问题#11195中的讨论提到Kafka偶尔会意外退出,并建议Spring-Kafka的安全管理器策略防止其这样做。这已经提交给Spring-Kafka,但是 - 如果我理解正确 - 没有提交给Gradle。


另一个边缘情况是关闭挂钩:调用 System.exit() 的线程将阻塞,直到JVM终止。如果关闭挂钩向此线程提交任务,则会导致死锁。

Abhijith,我可以问一下你为什么会遇到这个问题吗?据我所知,这不是Gradle的行为,而是与Spring-Kafka有关。这是你项目的后半部分,还是只是因为你的情况类似才提到了那个问题? - kriegaex
好的。当测试执行期间发生非零退出时,Gradle无法生成测试结果XML。(Gradle存在多个类似问题的开放问题)。我想要防止这种非零退出的调用发生在测试执行期间。因此,我不得不添加SecurityManager以确保生成测试结果。安全管理器只需在JVM中调用非零System.exit()方法时抛出异常即可。 - Abhijith Gururaj
你可能会问,那么为什么这个Java代理线程要调用System.exit()方法呢?为此,我添加了一些外部测试监听器,它们专门在超时情况下生成测试结果。我找不到其他保证从Gradle测试任务生成测试结果XML的方法。 - Abhijith Gururaj
例如,您可以浏览这个这个来了解安全管理器的需要以及一些额外的测试监听器,以确保无论如何都会生成测试结果。当测试任务失败时,测试的结果xml/html应该被生成,无论是错误的测试、超时还是某些二进制文件损坏。Ant可以做到这一点,但Gradle不行 :( - Abhijith Gururaj
感谢您的解释。这将有助于其他读者了解您为何要采取哪些措施来解决或减轻问题。 - kriegaex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接