Invoke RabbitListenerErrorHandler when the batch of the listener is enabled
jyxzwd opened this issue · comments
Expected Behavior
when @RabbitListener( batch = "true",errorHandler = "myErrorHandler"), i hope that when an exception is occurred then myErrorHandler can also be invoke just like when batch is false.
Current Behavior
when
listener:
@RabbitListener(queues = "abc",batch = "true",errorHandler = "myErrorHandler")
public void handle(List<Message> messages, Channel channel) {
throw new RuntimeException();
}
myErrorHandler:
@Component
public class MyErrorHandler implements RabbitListenerErrorHandler {
private static final Logger LOG = LoggerFactory.getLogger(MyErrorHandler.class);
@Override
public Object handleError(Message amqpMessage, org.springframework.messaging.Message<?> message, ListenerExecutionFailedException exception) throws Exception {
LOG.info("-------listener MyErrorHandler is occured-----------");
return null;
}
}
then ouput is
org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method 'public void org.exacple.listener.SimpleListener.handle(java.util.List<org.springframework.amqp.core.Message>,com.rabbitmq.client.Channel) throws java.io.IOException' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:286) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:224) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.adapter.BatchMessagingMessageListenerAdapter.onMessageBatch(BatchMessagingMessageListenerAdapter.java:80) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1612) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1535) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1523) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1518) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListenerAndHandleException(AbstractMessageListenerContainer.java:1459) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1440) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.executeWithList(SimpleMessageListenerContainer.java:1064) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1053) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:948) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1326) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1232) ~[spring-rabbit-3.0.5.jar:3.0.5]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Caused by: java.lang.RuntimeException: null
at org.exacple.listener.SimpleListener.handle(SimpleListener.java:114) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169) ~[spring-messaging-6.0.10.jar:6.0.10]
at org.springframework.amqp.rabbit.listener.adapter.KotlinAwareInvocableHandlerMethod.doInvoke(KotlinAwareInvocableHandlerMethod.java:45) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119) ~[spring-messaging-6.0.10.jar:6.0.10]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:75) ~[spring-rabbit-3.0.5.jar:3.0.5]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:274) ~[spring-rabbit-3.0.5.jar:3.0.5]
... 14 common frames omitted
There is no 'LOG.info("-------listener MyErrorHandler is occured-----------");', MyErrorHandler was not invoked.
Context
Then I checked the relevant source code:
if 'batch' is false, then In MessagingMessageListenerAdapter
// this method will be invoked when `batch` is false, RabbitListenerErrorHandler will be invoked in `handleException` method
public void onMessage(org.springframework.amqp.core.Message amqpMessage, Channel channel) throws Exception { // NOSONAR
Message<?> message = null;
try {
message = toMessagingMessage(amqpMessage);
invokeHandlerAndProcessResult(amqpMessage, channel, message);
}
catch (ListenerExecutionFailedException ex) {
handleException(amqpMessage, channel, message, ex);
}
catch (ReplyFailureException ex) { // NOSONAR
throw ex;
}
catch (Exception ex) { // NOSONAR
handleException(amqpMessage, channel, message, new ListenerExecutionFailedException(
"Failed to convert message", ex, amqpMessage));
}
}
private void handleException(org.springframework.amqp.core.Message amqpMessage, Channel channel,
@Nullable Message<?> message, ListenerExecutionFailedException e) throws Exception { // NOSONAR
if (this.errorHandler != null) {
try {
Message<?> messageWithChannel = null;
if (message != null) {
messageWithChannel = MessageBuilder.fromMessage(message)
.setHeader(AmqpHeaders.CHANNEL, channel)
.build();
}
Object errorResult = this.errorHandler.handleError(amqpMessage, messageWithChannel, e);
if (errorResult != null) {
Object payload = message == null ? null : message.getPayload();
InvocationResult invResult = payload == null
? new InvocationResult(errorResult, null, null, null, null)
: this.handlerAdapter.getInvocationResultFor(errorResult, payload);
handleResult(invResult, amqpMessage, channel, message);
}
else {
logger.trace("Error handler returned no result");
}
}
catch (Exception ex) {
returnOrThrow(amqpMessage, channel, message, ex, ex);
}
}
else {
returnOrThrow(amqpMessage, channel, message, e.getCause(), e);
}
}
if batch
is true
In BatchMessagingMessageListenerAdapter
then will throw an exception directly without invoke RabbitListenerErrorHandler
@Override
public void onMessageBatch(List<org.springframework.amqp.core.Message> messages, Channel channel) {
Message<?> converted;
if (this.converterAdapter.isAmqpMessageList()) {
converted = new GenericMessage<>(messages);
}
else {
List<Message<?>> messagingMessages = new ArrayList<>();
for (org.springframework.amqp.core.Message message : messages) {
messagingMessages.add(toMessagingMessage(message));
}
if (this.converterAdapter.isMessageList()) {
converted = new GenericMessage<>(messagingMessages);
}
else {
List<Object> payloads = new ArrayList<>();
for (Message<?> message : messagingMessages) {
payloads.add(message.getPayload());
}
converted = new GenericMessage<>(payloads);
}
}
try {
invokeHandlerAndProcessResult(null, channel, converted);
}
// here will throw an exception directly without invoke RabbitListenerErrorHandler
catch (Exception e) {
throw RabbitExceptionTranslator.convertRabbitAccessException(e);
}
}
So, Is there any hope that support for this case when batch
is true and RabbitListenerErrorHandler can alse be invoked will be added to future features?
The RabbitListenerErrorHandler
contract does not match with the batch:
Object handleError(Message amqpMessage, @Nullable org.springframework.messaging.Message<?> message,
ListenerExecutionFailedException exception) throws Exception;
in comparison with:
void onMessageBatch(List<Message> messages, Channel channel);
Since we have no knowledge with exactly message has failed in the batch when we call:
invokeHandlerAndProcessResult(null, channel, converted);
(see how an AMQP Message
is null
)
We leave the error handling here up to a generic ErrorHandler
on the listener container level.
WDYT?
The
RabbitListenerErrorHandler
contract does not match with the batch:Object handleError(Message amqpMessage, @Nullable org.springframework.messaging.Message<?> message, ListenerExecutionFailedException exception) throws Exception;
in comparison with:
void onMessageBatch(List<Message> messages, Channel channel);
Since we have no knowledge with exactly message has failed in the batch when we call:
invokeHandlerAndProcessResult(null, channel, converted);
(see how an AMQP
Message
isnull
) We leave the error handling here up to a genericErrorHandler
on the listener container level.WDYT?
I got it. Because we have no knowledge with exactly message has failed during porcessing, except in our custom listener code.
I think this is the reason. And thank u for explaning to me.