Hello @zarusz
Here's the description:
Context
When using the request/response (RPC) pattern on RabbitMQ with ConsumerMode.RequestResponse,
a deserialization failure on the handler queue silently discards the message (NACK without requeue)
but sends no response to the requester. The requester waits until its timeout expires with
no indication of what went wrong.
Current behaviour
The exception is thrown inside _messageProvider(messageType, messageHeaders, transportMessage)
in MessageProcessor<T>.ProcessMessage. The outer catch block catches it, sets
result = ProcessResult.Failure, and returns. The transport layer then NACKs the message.
Because the exception occurs before DoHandle is reached, neither the
IRabbitMqConsumerErrorHandler<T> nor any consumer-level error handler is ever invoked.
The handler's OnHandle method is never called, so there is no opportunity to send a
typed failure response to the requester.
_messageProvider(messageType, ...) ← throws JsonSerializationException
outer catch → result = Failure → NACK
DoHandle(message) ← never reached
└─ IRabbitMqConsumerErrorHandler ← never reached
└─ handler.OnHandle ← never reached → no response sent to requester
Expected behaviour
For request/response consumers, a deserialization failure should result in an error response
being sent back to the requester (e.g. via IResponseProducer.ProduceResponse with the
deserialization exception). This would:
- prevent the requester from timing out with no feedback
- allow user code (via
IRabbitMqConsumerErrorHandler<T> or equivalent) to decide on
the broker disposition (ACK or NACK)
Important constraint — Requeue must be excluded
A deserialization failure is a permanent failure: the message payload will never
deserialize successfully regardless of how many times it is retried. Allowing Requeue
as a valid response would produce an infinite retry loop.
The available actions should be limited to NACK (default) or ACK (silent discard).
Steps to reproduce
- Configure a RabbitMQ request/response handler for
TRequest where one field is typed as an enum.
- Publish a raw AMQP message with an unknown string value for that enum field
(e.g. {"ask_for": "unknown.value"}).
- Observe:
JsonSerializationException logged, message NACKed,
requester never receives a response and times out.
- Expected: requester receives a typed error response; message is NACKed
(or ACKed if the error handler decides so).
Additional observation — second identical message stayed unacked
During testing, sending a second identical malformed message produced a different
outcome: the deserialization failure occurred identically, but the message was
not NACKed and remained unacked indefinitely.
The message was only resolved when the channel was reset — either by a broker
maintenance event (CONNECTION_FORCED) or by restarting the consumer. In both
cases, RabbitMQ redelivered the unacked message, which then fell back into the
main scenario (deserialization failure → NACK → discarded, still no RPC response).
The root cause of the inconsistency between the first and second message (one
NACKed, one not) is unclear — it may be related to channel state after the first
failure.
Side effect: if basicQos prefetch count is low (e.g. 1), a stuck unacked
message blocks all subsequent deliveries to that consumer for the duration of
the window.
Version
SlimMessageBus 3.5.0 (also present in 3.4.0), transport SlimMessageBus.Host.RabbitMQ
Best regards!
Hello @zarusz
Here's the description:
Context
When using the request/response (RPC) pattern on RabbitMQ with
ConsumerMode.RequestResponse,a deserialization failure on the handler queue silently discards the message (NACK without requeue)
but sends no response to the requester. The requester waits until its timeout expires with
no indication of what went wrong.
Current behaviour
The exception is thrown inside
_messageProvider(messageType, messageHeaders, transportMessage)in
MessageProcessor<T>.ProcessMessage. The outercatchblock catches it, setsresult = ProcessResult.Failure, and returns. The transport layer then NACKs the message.Because the exception occurs before
DoHandleis reached, neither theIRabbitMqConsumerErrorHandler<T>nor any consumer-level error handler is ever invoked.The handler's
OnHandlemethod is never called, so there is no opportunity to send atyped failure response to the requester.
Expected behaviour
For request/response consumers, a deserialization failure should result in an error response
being sent back to the requester (e.g. via
IResponseProducer.ProduceResponsewith thedeserialization exception). This would:
IRabbitMqConsumerErrorHandler<T>or equivalent) to decide onthe broker disposition (ACK or NACK)
Important constraint — Requeue must be excluded
A deserialization failure is a permanent failure: the message payload will never
deserialize successfully regardless of how many times it is retried. Allowing
Requeueas a valid response would produce an infinite retry loop.
The available actions should be limited to NACK (default) or ACK (silent discard).
Steps to reproduce
TRequestwhere one field is typed as an enum.(e.g.
{"ask_for": "unknown.value"}).JsonSerializationExceptionlogged, message NACKed,requester never receives a response and times out.
(or ACKed if the error handler decides so).
Additional observation — second identical message stayed unacked
During testing, sending a second identical malformed message produced a different
outcome: the deserialization failure occurred identically, but the message was
not NACKed and remained unacked indefinitely.
The message was only resolved when the channel was reset — either by a broker
maintenance event (
CONNECTION_FORCED) or by restarting the consumer. In bothcases, RabbitMQ redelivered the unacked message, which then fell back into the
main scenario (deserialization failure → NACK → discarded, still no RPC response).
The root cause of the inconsistency between the first and second message (one
NACKed, one not) is unclear — it may be related to channel state after the first
failure.
Side effect: if
basicQosprefetch count is low (e.g. 1), a stuck unackedmessage blocks all subsequent deliveries to that consumer for the duration of
the window.
Version
SlimMessageBus
3.5.0(also present in3.4.0), transportSlimMessageBus.Host.RabbitMQBest regards!