spring-projects / spring-amqp

Spring AMQP - support for Spring programming model with AMQP, especially but not limited to RabbitMQ

Home Page:https://spring.io/projects/spring-amqp

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 is null) We leave the error handling here up to a generic ErrorHandler 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.