繁体   English   中英

如何在包含反应式管道创建者的堆栈跟踪的反应式管道的执行中引发异常?

[英]How can I throw an exception within an execution of a reactive pipeline that contains the stack trace of the creator of the reactive pipeline?

如果我运行以下反应式管道:

        Mono.empty()
                .switchIfEmpty(Mono.defer(() -> Mono.error(new RuntimeException())))
                .block();

我得到了反应步骤的堆栈跟踪( switchIfEmpty ); 但我对管道创建者的堆栈跟踪感兴趣。 反应式堆栈跟踪部分是无用的,我实际上对调试触发创建管道的请求(它是按顺序执行的)感兴趣。

即使管道创建者的堆栈跟踪有时会显示在反应堆栈元素下方,这也不能保证,因为我见过太多意外分叉的管道,最坏的情况是丢失了管道创建者堆栈跟踪。 最好的情况是感兴趣的堆栈跟踪被隐藏在堆栈元素下,以至于几乎不可能进行调试。

由于我创建的管道将用于通过网络执行往返请求,因此我可以接受一个解决方案,该解决方案要求支付的最大性能价格相当于执行网络请求往返成本的 1 个数量级。

解决方案非常简单:

  1. 在创建管道的同时创建异常; 但不要扔。 该异常将使用感兴趣的堆栈跟踪构造。
  2. 如果需要,使用构造的异常构建管道。

以下应该有效:

        Mono.empty()
                .switchIfEmpty(Mono.error(new RuntimeException()))
                .block();

这条管道在性能方面会更加昂贵; 但会尊重 OP 的要求,即不增加网络请求往返成本的一小部分。

以下测试用例验证并说明(检查打印输出的顺序)该技术获得所需堆栈跟踪的有效性:

import org.junit.jupiter.api.Test;

import java.io.PrintWriter;
import java.io.StringWriter;

import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

public class MonoSwitchIfEmptyTest {

    @Test
    void whenRequestThrowsPrecompiledException_thenExpectDesiredStackTrace() {
        trySwitchIfEmptyWithDesiredStackTrace(createAppropriateSwitchIfEmptyMono(true), true);
    }

    @Test
    void whenRequestThrowsLiveException_thenExpectUndesiredStackTrace() {
        trySwitchIfEmptyWithDesiredStackTrace(createAppropriateSwitchIfEmptyMono(false), false);
    }

    // This is done here to force the creation of an extra stack element into the stack trace.
    // That stack element is the one that later will be checked to determine if the test passed or not.
    private Mono<Void> createAppropriateSwitchIfEmptyMono(boolean desiredStackTrace) {
        return desiredStackTrace ?
                Mono.error(new LoggedRuntimeException()) :
                Mono.defer(() -> Mono.error(new LoggedRuntimeException()));
    }

    private void trySwitchIfEmptyWithDesiredStackTrace(Mono<Void> switchIfEmptyMono, boolean desiredStackTrace) {
        Mono<Void> loggedError = Mono.fromRunnable(() -> System.out.println("Throwing exception..."))
                .then(switchIfEmptyMono);

        Mono<Object> testedPipeline = Mono.fromRunnable(() -> loggedBusyWork(1000))
                .switchIfEmpty(loggedError);

        StepVerifier.create(testedPipeline)
                .expectErrorMatches(e -> ((LoggedRuntimeException)e).isRelevantStackTrace() == desiredStackTrace)
                .verify();
    }

    private void loggedBusyWork(int millis) {
        long startTime = System.currentTimeMillis();
        System.out.println("Starting busy work @ " + startTime + "...");
        while (System.currentTimeMillis() - startTime < millis);
        System.out.println("End busy work @ " + System.currentTimeMillis());
    }

    static class LoggedRuntimeException extends RuntimeException {

        public LoggedRuntimeException() {
            System.out.println("Creating exception...");
            String stackTrace = getStackTraceStr();
            System.out.println("Stack trace: \n" + stackTrace);
        }

        private String getStackTraceStr() {
            StringWriter writer = new StringWriter();
            printStackTrace(new PrintWriter(writer));
            return writer.toString();
        }

        public boolean isRelevantStackTrace() {
            return getStackTrace()[1].toString().contains(MonoSwitchIfEmptyTest.class.getName());
        }
    }
}

GitHub 上的完整代码

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM