I want to catch exceptions thrown from a flux, my code is like this:
try {
Flux.just("key1", "key2", "key3")
.doOnNext(System.out::println)
.map(k -> {
if (!k.equals("key1")) {
throw new RuntimeException("Not key1"); // 1
}
return "External Value, key:" + k;
})
.subscribe(System.out::println); // 2
} catch (Throwable e) {
System.out.println("Got exception"); // 3
}
the output is:
key1
External Value, key:key1
key2
[ERROR] (main) Operator called default onErrorDropped - reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.RuntimeException: Not key1
reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.RuntimeException: Not key1
Caused by: java.lang.RuntimeException: Not key1
at com.cxd.study.reactor.HandlingErrors.lambda$catchException$7(HandlingErrors.java:153)
...
It seems that my catch at step 3 is never reached.
I know I can reach the exception at step 2 like this:.subscribe(System.out::println, e -> System.out.println("Got Exception")).
But how can I catch the exception thrown at step 1 out of the flux?
You can use the onError() operator to handle error cases, or the doOnError() operator if you e.g. want to log the exception.
Related
my case is showed as below:
I have two web requests named request1 and request2, the input of request2 comes from the output of request1. Now I want to set timeout for both of these two requests. Ideally, the time cost of request1 is 2s and the time cost of request2 is 3s. So I want to set the timeout of request1 as 3s and timeout of request2 as 4s. as the documentation of Mono#timeout said, I think it's possible to be done. But unfortunately the second timeout is calculated by accumulation. So I'm confused about the the meaning of this mono.
documentation of Mono#timeout(Duration timeout)(https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#timeout-java.time.Duration-)
public final Mono<T> timeout(Duration timeout)
Propagate a TimeoutException in case no item arrives within the given Duration.
Parameters:
timeout - the timeout before the onNext signal from this Mono
Returns: a Mono that can time out
sample code of my case:
Mono<String> startMono = Mono.just("start");
String result = startMono
.map(x -> {
log.info("received message: {}", x);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "#1 enriched: " + x;
})
.timeout(Duration.ofSeconds(3))
.onErrorResume(throwable -> {
log.warn("Caught exception, apply fallback behavior #1", throwable);
return Mono.just("item from backup #1");
})
.map(y -> {
log.info("received message: {}", y);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "#2 enriched: " + y;
})
.timeout(Duration.ofSeconds(4))
// there is no timeoutException thrown if I set the second timeout to 6s (6s > 2s + 3s)
// .timeout(Duration.ofSeconds(6))
.onErrorResume(throwable -> {
log.warn("Caught exception, apply fallback behavior #2", throwable);
return Mono.just("item from backup #2");
})
.block();
log.info("result: {}", result);
exception thrown from the above code:
16:46:51.080 [main] INFO MonoDemo - received message: start
16:46:53.095 [elastic-2] INFO MonoDemo - received message: #1 enriched: start
16:46:55.079 [parallel-1] WARN MonoDemo - Caught exception, apply fallback behavior #2
java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 4000ms in 'flatMap' (and no fallback has been configured)
at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:288) [reactor-core-3.3.10.RELEASE.jar:3.3.10.RELEASE]
at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:273) [reactor-core-3.3.10.RELEASE.jar:3.3.10.RELEASE]
at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:395) [reactor-core-3.3.10.RELEASE.jar:3.3.10.RELEASE]
at reactor.core.publisher.StrictSubscriber.onNext(StrictSubscriber.java:89) [reactor-core-3.3.10.RELEASE.jar:3.3.10.RELEASE]
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) [reactor-core-3.3.10.RELEASE.jar:3.3.10.RELEASE]
at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:117) [reactor-core-3.3.10.RELEASE.jar:3.3.10.RELEASE]
at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) [reactor-core-3.3.10.RELEASE.jar:3.3.10.RELEASE]
at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) [reactor-core-3.3.10.RELEASE.jar:3.3.10.RELEASE]
at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
at java.lang.Thread.run(Thread.java:834) [?:?]
16:46:55.095 [main] INFO MonoDemo - result: item from backup #2
The timeout operator measures the time from the subscription until the arrival of the first signal. More on this here.
If you'd like to apply the timeout on the second operation only then you need to put timeout operator in a place where only the second request is in scope. See the following:
public void execute() {
firstRequest()
.onErrorResume(throwable -> secondRequest())
.onErrorReturn("some static fallback value if second failed as well")
.block();
}
private Mono<String> firstRequest() {
return Mono.delay(Duration.ofSeconds(2))
.thenReturn("first")
.timeout(Duration.ofSeconds(3));
// additional mapping can be done here related to first request
}
private Mono<String> secondRequest() {
return Mono.delay(Duration.ofSeconds(3))
.thenReturn("second")
.timeout(Duration.ofSeconds(4));
// additional mapping can be done here related to second request
}
By moving the timeout operator inside the private methods we ensure that only the duration of the those particular Monos are measured and not the whole chain.
I am using the resilience4j library to retry some code, I have the following code below, I expect it to run 4 times. If I throw IllegalArgumentException it works but if I throw ConnectException it doesn't.
object Test extends App {
val retryConf = RetryConfig.custom()
.maxAttempts(4)
.retryOnException(_ => true)
//.retryExceptions(classOf[ConnectException])
.build
val retryRegistry = RetryRegistry.of(retryConf)
val retryConfig = retryRegistry.retry("test", retryConf)
val supplier: Supplier[Unit] = () => {
println("Run")
throw new IllegalArgumentException("Test")
//throw new ConnectException("Test")
}
val decoratedSupplier = Decorators.ofSupplier(supplier).withRetry(retryConfig).get()
}
I expected that retry to retry on all exceptions.
You are creating decorated supplier which is only catching RuntimeExceptions whilst ConnectException is not a RuntimeException:
... decorateSupplier(Retry retry, Supplier<T> supplier) {
return () -> {
Retry.Context<T> context = retry.context();
do try {
...
} catch (RuntimeException runtimeException) {
...
Look through the Retry.java and choose one that catches Exception for example decorateCheckedFunction, for example
val registry =
RetryRegistry.of(RetryConfig.custom().maxAttempts(4).build())
val retry = registry.retry("my")
Retry.decorateCheckedFunction(retry, (x: Int) => {
println(s"woohoo $x")
throw new ConnectException("Test")
42
}).apply(1)
which outputs
woohoo 1
woohoo 1
woohoo 1
woohoo 1
Exception in thread "main" java.rmi.ConnectException: Test
Personally I use softwaremill/retry
Playing with PersistenceException -> SQLException -> getErrorCode(), I'm hiding a specific Oracle error in my log. Problem is, in my server.log I still found rows like:
WARN 25 Sep 2018 12:14:47,121 - id - org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ms 5829302 SQL Error: 13333, SQLState: 72000
ERROR 25 Sep 2018 12:14:47,121 - id - org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ms 5829302 ORA-13333: invalid LRS measure
ORA-06512: at "MDSYS.SDO_LRS", line 3149
ORA-06512: at line 1
which aren't specifically logged by me.
Here is my code:
try {
try {
//business stuff
} catch (PersistenceException persEx) {
if (persEx.getCause() != null && persEx.getCause() instanceof GenericJDBCException) {
GenericJDBCException jdbcEx = (GenericJDBCException) persEx.getCause();
SQLException sqlEx = (SQLException) jdbcEx.getCause();
if (sqlEx.getErrorCode() == 13333) {
//handling ORA-13333: invalid LRS measure as an info
log.info("Possible invalid LRS measure");
} else {
throw persEx; // do not swallow unhandled exceptions
}
} else {
throw persEx; // do not swallow unhandled exceptions
}
} catch (Exception e) {
log.error("Other exception:" + e.getMessage());
}
} catch (Exception e) {
log.error("Exception to log:" + e.getMessage());
}
Solved by adding a specific filter to the JBoss log handler, in this case
<filter-spec value="all(not(match("ORA-13333")), not(match("SQL Error: 13333")))"/>
I hope it can be useful to someone else.
I threw two exceptions in a flowable, and I used onErrorReturn to catch those exceptions. But I found that only the first exception was caught. How to catch all the exceptions?
Flowable.create(emitter -> {
emitter.onError(new Exception("error1"));
emitter.onError(new Exception("error2"));
}, BackpressureStrategy.MISSING)
.onErrorReturn(e -> {
System.out.println("Got error " + e.getMessage());
return "error";
})
.subscribe();
Output:
Got error error1
io.reactivex.exceptions.UndeliverableException: java.lang.Exception: error2
at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:349)
at io.reactivex.internal.operators.flowable.FlowableCreate$BaseEmitter.onError(FlowableCreate.java:271)
at com.example.springboottest.SimpleApplication.lambda$main$0(SimpleApplication.java:48)
at io.reactivex.internal.operators.flowable.FlowableCreate.subscribeActual(FlowableCreate.java:72)
at io.reactivex.Flowable.subscribe(Flowable.java:13094)
at io.reactivex.internal.operators.flowable.FlowableOnErrorReturn.subscribeActual(FlowableOnErrorReturn.java:33)
at io.reactivex.Flowable.subscribe(Flowable.java:13094)
at io.reactivex.Flowable.subscribe(Flowable.java:13030)
at io.reactivex.Flowable.subscribe(Flowable.java:12890)
at com.example.springboottest.SimpleApplication.main(SimpleApplication.java:54)
Caused by: java.lang.Exception: error2
... 8 more
Exception in thread "main" io.reactivex.exceptions.UndeliverableException: java.lang.Exception: error2
at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:349)
at io.reactivex.internal.operators.flowable.FlowableCreate$BaseEmitter.onError(FlowableCreate.java:271)
at com.example.springboottest.SimpleApplication.lambda$main$0(SimpleApplication.java:48)
at io.reactivex.internal.operators.flowable.FlowableCreate.subscribeActual(FlowableCreate.java:72)
at io.reactivex.Flowable.subscribe(Flowable.java:13094)
at io.reactivex.internal.operators.flowable.FlowableOnErrorReturn.subscribeActual(FlowableOnErrorReturn.java:33)
at io.reactivex.Flowable.subscribe(Flowable.java:13094)
at io.reactivex.Flowable.subscribe(Flowable.java:13030)
at io.reactivex.Flowable.subscribe(Flowable.java:12890)
at com.example.springboottest.SimpleApplication.main(SimpleApplication.java:54)
Caused by: java.lang.Exception: error2
... 8 more
An Observable ends after it completes or encounters an error. This is a part of the contract and there is no way around that property.
The only way you could handle 'multiple' exceptions in an observable is if it is handled like a value, not an exception. This would end up looking like Scala's Either construct in practice.
I know that the line in error is to_return = find(list,false); How can I get the line number of this line when there is NullPointerException type of error? Or in line number in general?
I tried few things. The closest is this one Called.getLineNumber() which gives me the line number of StackTraceElement Called = new Throwable().fillInStackTrace().getStackTrace()[0];
public TestObject[] myfind(Subitem list )throws Exception{
TestObject[]to_return=null;
try {
to_return = find(list,false);
}
catch (RationalTestException ex) {
//logStoreException(ex);
StackTraceElement Called = new Throwable().fillInStackTrace().getStackTrace()[0];
StackTraceElement Calling = new Throwable().fillInStackTrace().getStackTrace()[1];
throw new Exception (this.add_debugging_info(Called, Calling, ex.getMessage()));
}
catch (NullPointerException npe) {
StackTraceElement Called = new Throwable().fillInStackTrace().getStackTrace()[0];
StackTraceElement Calling = new Throwable().fillInStackTrace().getStackTrace()[1];
logStoreException(npe);
System.out.println("Line number: "+npe.getStackTrace()[0].getLineNumber());
System.out.println("Line number2: "+Integer.toString(Called.getLineNumber()));
System.out.println(this.add_debugging_info(Called, Calling, npe.getMessage()));
throw new Exception (this.add_debugging_info(Called, Calling, npe.getMessage()));
}
catch (Exception ex) {
StackTraceElement Called = new Throwable().fillInStackTrace().getStackTrace()[0];
StackTraceElement Calling = new Throwable().fillInStackTrace().getStackTrace()[1];
throw new Exception (this.add_debugging_info(Called, Calling, ex.getMessage()));
}
finally {
//unregisterAll();
//unregister(to);
return to_return;
}
}
If you just want the current stack trace, use Thread.currentThread().getStackTrace()
If you want to force the JVM to fill in the stack traces, please set the option -XX:-OmitStackTraceInFastThrow on your JVM.
When running in Eclipse, to get the line number itself, you need to get the StackTrace array and call getLineNumber() on it.
The following worked for me in my Utils class, isSessionConnectible method:
... catch (ClassFormatError cfe) {
logger.error("Problem ClassFormatError connecting. " + cfe.getMessage() + " " + cfe.getCause());
int size = cfe.getStackTrace().length - 1;
logger.error(" Root cause: " + cfe.getStackTrace()[size].getMethodName() + " " + cfe.getStackTrace()[size].getClassName());
if (size>1) {
logger.error(" Penultimate cause: method=" + cfe.getStackTrace()[size-1].getMethodName() + " class=" + cfe.getStackTrace()[size-1].getClassName() +
" line=" + cfe.getStackTrace()[size-1].getLineNumber());
}
Result when thrown:
2018-07-06 12:00:12 ERROR Utils:319 - Problem ClassFormatError connecting to Hibernate. Absent Code attribute in method that is not native or abstract in class file javax/transaction/SystemException null
2018-07-06 12:00:12 ERROR Utils:322 - Root cause: main <mypackage>.Utils
2018-07-06 12:00:12 ERROR Utils:324 - Penultimate cause: method=isSessionConnectible class=<mypackage>.Utils line=306
BTW, in Eclipse, make sure that Window --> Preferences --> Java --> Compiler has the checkbox marked at "Add line number attributes to generated class files".