Reactor 애플리케이션에서 예외가 발생하면 main 메소드로부터 예외 발생 지점까지의 호출 순서를 제대로 확인하기가 어려움
이것은 reactor의 스케쥴러가 멀티쓰레드 기반이라서 스케쥴을 분리할 때마다 멀티쓰레드가 분리되면서 호출 순서가 끊어지기 때문임
다음 순서로 호출됨
public class SimpleFluxTest {
public static void main(String[] args) {
PoliteServer server = new PoliteServer(new KitchenService());
server.doingMyJob().subscribe();
}
}
public class PoliteServer {
private final KitchenService kitchen;
PoliteServer(KitchenService kitchen) {
this.kitchen = kitchen;
}
Flux<Dish> doingMyJob() {
return this.kitchen.getDishes()
.doOnNext(dish -> System.out.println("Thank you for " + dish + "!"))
.doOnError(error -> {
System.out.println("So sorry about " + error.getMessage());
error.printStackTrace();
})
.doOnComplete(() -> System.out.println("Thanks for all your hard work!"))
.map(Dish::deliver);
}
}
PoliteServier.doingMyJob()의 doOnError()에서 예외 발생 시 stacktrace를 출력하게 했음
public class KitchenService {
Flux<Dish> getDishes() {
return Flux.just(new Dish("Sesame chicken"),
new Dish("Lo mein noodles, plain"),
null,
new Dish("Sweet & sour beef"));
}
}
KitchenService.getDishes()는 Flux 스트림을 생성하는 역할만 담당함. 중간에 일부러 null을 끼워넣었음
java.lang.NullPointerException: The 1th array element was null
at reactor.core.publisher.FluxArray$ArraySubscription.fastPath(FluxArray.java:168)
at reactor.core.publisher.FluxArray$ArraySubscription.request(FluxArray.java:97)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:169)
at reactor.core.publisher.LambdaSubscriber.onSubscribe(LambdaSubscriber.java:119)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:96)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178)
at reactor.core.publisher.FluxArray.subscribe(FluxArray.java:53)
at reactor.core.publisher.FluxArray.subscribe(FluxArray.java:59)
at reactor.core.publisher.Flux.subscribe(Flux.java:8469)
at reactor.core.publisher.Flux.subscribeWith(Flux.java:8642)
at reactor.core.publisher.Flux.subscribe(Flux.java:8439)
at reactor.core.publisher.Flux.subscribe(Flux.java:8363)
at reactor.core.publisher.Flux.subscribe(Flux.java:8281)
at com.terzeron.grammar.SimpleFluxTest.main(SimpleFluxTest.java:6)
main()에서 subscribe() 호출만 있을 뿐, 애플리케이션 코드 상의 호출 관계가 전혀 보이지 않음
main()에 Hooks.onOperatorDebug()를 추가하면 자세한 호출 순서를 가진 stacktrace를 살펴볼 수 있음
public static void main(String[] args) {
Hooks.onOperatorDebug();
PoliteServer server = new PoliteServer(new KitchenService());
server.doingMyJob().subscribe();
}
다음과 같이 stacktrace가 달라짐
java.lang.NullPointerException: The 1th array element was null
at reactor.core.publisher.FluxArray$ArraySubscription.fastPath(FluxArray.java:168)
Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below:
Assembly trace from producer [reactor.core.publisher.FluxArray] :
reactor.core.publisher.Flux.just(Flux.java:1333)
com.terzeron.grammar.KitchenService.getDishes(KitchenService.java:7)
Error has been observed at the following site(s):
*______Flux.just ⇢ at com.terzeron.grammar.KitchenService.getDishes(KitchenService.java:7)
|_ Flux.doOnNext ⇢ at com.terzeron.grammar.PoliteServer.doingMyJob(PoliteServer.java:14)
Original Stack Trace:
at reactor.core.publisher.FluxArray$ArraySubscription.fastPath(FluxArray.java:168)
at reactor.core.publisher.FluxArray$ArraySubscription.request(FluxArray.java:97)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.request(FluxPeekFuseable.java:144)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:169)
at reactor.core.publisher.LambdaSubscriber.onSubscribe(LambdaSubscriber.java:119)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:96)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onSubscribe(FluxPeekFuseable.java:178)
at reactor.core.publisher.FluxArray.subscribe(FluxArray.java:53)
at reactor.core.publisher.FluxArray.subscribe(FluxArray.java:59)
at reactor.core.publisher.Flux.subscribe(Flux.java:8469)
at reactor.core.publisher.Flux.subscribeWith(Flux.java:8642)
at reactor.core.publisher.Flux.subscribe(Flux.java:8439)
at reactor.core.publisher.Flux.subscribe(Flux.java:8363)
at reactor.core.publisher.Flux.subscribe(Flux.java:8281)
at com.terzeron.grammar.SimpleFluxTest.main(SimpleFluxTest.java:9)
main()부터 doingMyJob()을 거쳐 getDishes(), Flux.just()까지 호출되고 여기서 null이 포함되어 NullPointerException이 발생했음을 확인할 수 있음
Hooks.onOperatorDebug()가 디버깅에 도움을 줄 수 있긴 하지만, 예외 발생 시 성능 저하의 원인이 될 수 있어서 Spring Framework 개발진은 최후의 수단으로 사용할 것을 권고하고 있음
https://github.com/spring-projects/spring-framework/issues/22007