ARnikev
01/29/2019, 10:17 AMfun doSomething(someBody: Body): Mono<Void> {
return client
.patch()
.uri("/someuri")
.accept(APPLICATION_JSON_UTF8)
.contentType(APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(someBody))
.retrieve()
.bodyToMono(Void::class.java)
.onErrorMap {
BusinessServiceException("CODE", "TITLE").apply {
httpStatus = HttpStatus.FORBIDDEN
detail = "some detail"
}
}
}
To invoke this method in non-blocking way i do following thing:
try {
clientBean.doSomething(someBody).awaitFirst()
} catch (e: Exception) {
throw e
}
What i expect is that in case of exception in webclient call there has to be BusinessServiceException caught in catch block above.
And BusinessServiceException fields (code, title, httpStatus, detail) have to be populated with values that i’ve set in onErrorMap method on webclient mono response.
What i really have is BusinessServiceException with null fields (code, title, httpStatus, detail) and cause field populated with expected by me BusinessServiceException (with all fields filled the same way as i’ve set them in onErrorMap method).
BusinessServiceException class looks like this:
public class BusinessServiceException extends ServiceException {
public BusinessServiceException(String code, String title) {
super(code, title);
}
public BusinessServiceException(String message, Throwable cause) {
super(message, cause);
}
}
I’ve tried to debug all this stuff and it seems like that BusinessServiceException’s constructors called 2 times.
First time, as expected, the public BusinessServiceException(String code, String title)
constructor is called, in onErrorMap() method.
Second time, unexpectedly, the public BusinessServiceException(String message, Throwable cause)
constructor is called, somewhere in private suspend fun <T> Publisher<T>.awaitOne -> override fun onError(e: Throwable) { cont.resumeWithException(e)}
method, and that call wraps original exception in exception of the same type but with null fields (because of wrong constructor call).
I couldn’t find the exact place where this constructor could be called.
Interesting thing is if i delete this second constructor public BusinessServiceException(String message, Throwable cause)
from the BusinessServiceException.class - everything works as expected.
I would really appreciate any thoughts on this.ARnikev
01/29/2019, 12:53 PM/**
* Tries to recover stacktrace for given [exception] and [continuation].
* Stacktrace recovery tries to restore [continuation] stack frames using its debug metadata with [CoroutineStackFrame] API
* and then reflectively instantiate exception of given type with original exception as a cause and
* sets new stacktrace for wrapping exception.
* Some frames may be missing due to tail-call elimination.
*
* Works only on JVM with enabled debug-mode.
*/
internal expect fun <E: Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E
Exception is instantiated using reflection by using no-arg, cause or cause and message constructor.
It uses different constructor for exception recovery and therefore loses original exception’s fields data.Evgeniy Zaharov
01/29/2019, 1:24 PMVsevolod Tolstopyatov [JB]
01/29/2019, 2:14 PMARnikev
01/29/2019, 5:14 PMVsevolod Tolstopyatov [JB]
01/30/2019, 12:00 PMprivate
and replace it with factory function:
class MyException private constructor(args)
// Factory that looks like constructor
fun MyException(args): MyException = ...