簡體   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