How to get original service's exception on Apache Ignite client? - kotlin

Ley's say we have a service:
interface ExceptionService : Service {
fun doSmth(flag: Boolean)
}
implementation:
class ExceptionServiceImpl : ExceptionService {
override fun doSmth(flag: Boolean) {
if (flag) {
throw IOException("my IO exception")
} else {
throw IllegalArgumentException("my IllegalArgument exception")
}
}
}
We deploy it onto Ignite cluster:
Ignition.start(
IgniteConfiguration()
.setServiceConfiguration(
ServiceConfiguration()
.setService(ExceptionServiceImpl())
.setName("service")
)
)
And now we call it from client:
val client = Ignition.startClient(ClientConfiguration().setAddresses("127.0.0.1"))
val service = client.services().serviceProxy("service", ExceptionService::class.java)
try {
service.doSmth(true)
} catch (e: Exception) {
println(e.mesaage)
}
try {
service.doSmth(false)
} catch (e: Exception) {
println(e.mesaage)
}
Console output will be:
Ignite failed to process request [1] my IO exception (server status code [1])
Ignite failed to process request [2] my IllegalArgument exception (server status code [1])
The problem is that the type of the caught exception is always org.apache.ignite.client.ClientException. The only thing that left from the original exceptions thrown on server is message, and even it is wrapped in other words. The cause is of type org.apache.ignite.internal.client.thin.ClientServerError. Types are lost.
I want to handle on client side different types of exceptions thrown by service. Is there a way to do it? Maybe some Ignite configuration that I missed?

Try enabling ThinClientConfiguration#sendServerExceptionStackTraceToClient

Related

Ktor doesn't log exceptions

Ktor (1.4.2) seems to be suppresing exceptions. According to documentation I need to re-throw exception like this
install(StatusPages) {
exception<Throwable> { ex ->
val callstack: String = ....
call.respond(HttpStatusCode.InternalServerError,
ApiError(ex.javaClass.simpleName, ex.message ?: "Empty Message", callstack)
)
throw ex
}
}
I wrote wrapper around Logger interface to make sure it's calling Logger.error() (it doesn't) and configured it like this
install(CallLogging) {
level = org.slf4j.event.Level.INFO
logger = Log4j2Wrapper(logger2)
}
I can see INFO-level logs. What am I missing ?
Turned out the problem is in rather akward design of ktor api. Summary of my findings:
Contrary to common expectation to see log events of specified log level and above it doesn't that way with CallLogging. It expects exact match of the logging level which minimizes usefulness of this feature.
You need to overwrite logger specified in ApplicationEnvironment.log. But if you follow official ktor examples and do it like this
val server = embeddedServer(Netty, port) {
applicationEngineEnvironment {
log = YOUR_LOGGER
}
}
it won't work because value call.application.environment inside function io.ktor.server.engine.defaultEnginePipeline(env) is still original one, created from logger name in config file or NOP logger.
Hence you'll need to initialize your server like this
val env = applicationEngineEnvironment {
log = YOUR_LOGGER
module {
install(CallLogging) {
level = org.slf4j.event.Level.INFO
}
install(StatusPages) {
exception<Throwable> { ex ->
val callstack: String = ...
call.respond(HttpStatusCode.InternalServerError,
ApiError(ex.javaClass.simpleName, ex.message ?: "Empty Message", callstack)
)
throw ex
}
}
routing {
}
}
connector {
host = "0.0.0.0"
port = ...
}
}
val server = embeddedServer(Netty, env)

Handling connection errors in Spring reactive webclient

I have a spring webclient making http calls to an external service and backed by reactive circuit breaker factory (resilience4J impl). WebClient and circuit breaker behave as expected when the client establishes connection and fails on response (Any internal server or 4XX errors). However, if the client fails to establish connection, either Connection Refused or UnknownHost, it starts to break down.
I cannot seem to catch the error message within the webclient and trigger circuit breaker.
Circuit breaker never opens and throws TimeoutException.
java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 1000ms in 'circuitBreaker' (and no fallback has been configured) .
Error from Web client.
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:9000 .
Here's my code. I have pasted the error origins as well. I have tried to map ConnectException to my custom exception for circuit breaker to pick up but, it did not work. Can someone help me on handling errors without the response from remote server?
public Mono<String> toSink(
Envelope envelope, ConsumerConfiguration webClientConfiguration) {
return getWebClient()
.post()
.uri(
uriBuilder -> {
if (webClientConfiguration.getPort() != null) {
uriBuilder.port(webClientConfiguration.getPort());
}
return uriBuilder.path(webClientConfiguration.getServiceURL()).build();
})
.headers(
httpHeaders ->
webClientConfiguration.getHttpHeaders().forEach((k, v) -> httpHeaders.add(k, v)))
.bodyValue(envelope.toString())
.retrieve()
.bodyToMono(Map.class)
// Convert 5XX internal server error and throw CB exception
.onErrorResume(
throwable -> {
log.error("Inside the error resume callback of webclient {}", throwable.toString());
if (throwable instanceof WebClientResponseException) {
WebClientResponseException r = (WebClientResponseException) throwable;
if (r.getStatusCode().is5xxServerError()) {
return Mono.error(new CircuitBreakerOpenException());
}
}
return Mono.error(new CircuitBreakerOpenException());
})
.map(
map -> {
log.info("Response map:{}", Any.wrap(map).toString());
return Status.SUCCESS.name();
})
.transform(
it -> {
ReactiveCircuitBreaker rcb =
reactiveCircuitBreakerFactory.create(
webClientConfiguration.getCircuitBreakerId());
return rcb.run(
it,
throwable -> {
/// "Did not observe any item or terminal signal within 1000ms.. " <--- Error here
log.info("throwable in CB {}", throwable.toString());
if (throwable instanceof CygnusBusinessException) {
return Mono.error(throwable);
}
return Mono.error(
new CircuitBreakerOpenException(
throwable, new CygnusContext(), null, null, null));
});
})
///io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:9000 <-- Error prints here
.onErrorContinue((throwable, o) -> log.error(throwable.toString()))
.doOnError(throwable -> log.error("error from webclient:{}", throwable.toString()));
}
I fixed it by adding an onErrorContinue block and re-throwing the exception as a custom that gets handled in my circuit breaker code.
.onErrorContinue(
(throwable, o) -> {
log.info("throwable => {}", throwable.toString());
if (throwable instanceof ReadTimeoutException || throwable instanceof ConnectException) {
throw new CircuitBreakerOpenException();
}
})
I would make the following suggestions on your solution:
1- There is an alternate variation of onErrorContinue which accepts a predicate so you can define which exceptions this operator will be applied to - Docs
2- Return a Mono.error instead of throwing RuntimeExceptions from Mono/Flux operators. This other stackoverflow answer covers this quite well - Stackoverflow
3- Perform logging with side effect operators (doOn*)
.doOnError(throwable -> log.info("throwable => {}", throwable.toString()))
.onErrorResume(throwable -> throwable instanceof ReadTimeoutException || throwable instanceof ConnectException,
t -> Mono.error(new CircuitBreakerOpenException()))
Hope this is helpful.

retryWhen used with flatMap throws exception

I wanted to retry in case of any exception from the service. But when using retryWhen am getting exception java.lang.IllegalStateException: UnicastProcessor allows only a single Subscriber.
Without retry, its working fine
Flux.window(10)
.flatMap(
windowedFlux ->
webclient.post().uri(url)
.body(BodyInserters.fromPublisher(windowedFlux, Request.class))
.exchange()
.doOnNext(ordRlsResponse -> {
if( ordRlsResponse.statusCode().is2xxSuccessful()) {
Mono<ResponseEntity<Response>> response = ordRlsResponse.toEntity(Response.class);
//doing some processing here
}
else {
throw new CustomeException(errmsg);
}
}).retryWhen(retryStrategy)).subscribe();
And my retryStrategy is defined like:
Retry retryStrategy = Retry.fixedDelay((long)5, Duration.ofSeconds((long)5))
.filter(exception -> exception instanceof CustomeException)
.doAfterRetry( exception -> log.info("Retry attempted"))

How to verify exception thrown using StepVerifier in project reactor

def expectError() {
StepVerifier.create(readDB())
.expectError(RuntimeException.class)
.verify();
}
private Mono<String> readDB() {
// try {
return Mono.just(externalService.get())
.onErrorResume(throwable -> Mono.error(throwable));
// } catch (Exception e) {
// return Mono.error(e);
// }
}
unable to make it work if externalService.get throws Exception instead of return Mono.error. Is is always recommended to transform to Mono/Flow using try catch or is there any better way to verify such thrown exception?
Most of the time, if the user-provided code that throws an exception is provided as a lambda, exceptions can be translated to onError. But here you're directly throwing in the main thread, so that cannot happen

What is the cleanest way to listen to JMS from inside a Spring-batch step?

Spring batch documentation recommends using the JmsItemReader, which is a wrapper around the JMSTemplate. However, I have discovered that the JMSTemplate has some issues - see http://activemq.apache.org/jmstemplate-gotchas.html .
This post came to my attention only because the queue was appearing to disappear before I could actually read the data of of it. The opportunity to miss messages seems like a fairly significant issue to me.
For consumers atleast try using DefaultMessageListenerContainer coupled with a SingleConnectionFactory or any such connection factory , it not need a scheduler to wake them up.there are log of examples explaining this , this one is really good in explaining stuff
http://bsnyderblog.blogspot.com/2010/05/tuning-jms-message-consumption-in.html
Here is the solution I ended up with. Since the query was about the "cleanest" way to listen to JMS from within a spring-batch step, I'm going to leave the question open for a while longer just in case there's a better way.
If someone can figure out why the code isn't formatting correctly, please let me know how to fix it.
1. In the a job listener, implement queue setup and teardown inside the beforeJob and afterJob events, respectively:
public void beforeJob(JobExecution jobExecution) {
try {
jobParameters = jobExecution.getJobParameters();
readerConnection = connectionFactory.createConnection();
readerConnection.start();
} catch (JMSException ex) {
// handle the exception as appropriate
}
}
public void afterJob(JobExecution jobExecution) {
try {
readerConnection.close();
} catch (JMSException e) {
// handle the exception as appropriate
}
}
2. In the reader, implement the StepListener and beforeStep / afterStep methods.
public void beforeStep(StepExecution stepExecution) {
this.stepExecution = stepExecution;
this.setJobExecution(stepExecution.getJobExecution());
try {
this.connection = jmsJobExecutionListener.getReaderConnection();
this.jmsSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
this.messageConsumer = jmsSession.createConsumer(jmsJobExecutionListener.getQueue());
}
catch (JMSException ex)
{
// handle the exception as appropriate
}
}
public ExitStatus afterStep(StepExecution stepExecution) {
try {
messageConsumer.close();
jmsSession.close();
} catch (JMSException e) {
// handle the exception as appropriate
}
return stepExecution.getExitStatus();
}
3. Implement the read() method:
public TreeModel<SelectedDataElementNode> read() throws Exception,
UnexpectedInputException, ParseException,
NonTransientResourceException {
Object result = null;
logger.debug("Attempting to receive message on connection: ", connection.toString());
ObjectMessage msg = (ObjectMessage) messageConsumer.receive();
logger.debug("Received: {}", msg.toString());
result = msg.getObject();
return result;
}
4. Add the listeners to the Spring Batch context as appropriate:
<batch:job id="doStuff">
<batch:listeners>
<batch:listener ref="jmsJobExecutionListener" />
</batch:listeners>
... snip ...
<batch:step id="step0003-do-stuff">
<batch:tasklet transaction-manager="jtaTransactionManager"
start-limit="100">
<batch:chunk reader="selectedDataJmsReader" writer="someWriter"
commit-interval="1" />
</batch:tasklet>
<batch:listeners>
<batch:listener ref="selectedDataJmsReader" />
</batch:listeners>
</batch:step>
</batch:job>