shashank created CAMEL-25793:
--------------------------------
Summary: camel-paho-mqtt5 - with manualAcksEnabled a failed
message is never retried, and after receiveMaximum failures the consumer
receives nothing more
Key: CAMEL-25793
URL: https://issues.apache.org/jira/browse/CAMEL-25793
Project: Camel
Issue Type: Bug
Components: camel-paho-mqtt5
Reporter: shashank
Assignee: shashank
{{PahoMqtt5Consumer}} (main 7aae27315946) with {{manualAcksEnabled=true}} adds
an on-completion to every exchange ({{createExchange}}, lines 248-268):
{{onComplete}} calls {{client.messageArrivedComplete(id, qos)}} (the PUBACK /
PUBREC), {{onFailure}} only logs:
{code:java}
@Override
public void onFailure(Exchange exchange) {
LOG.debug("Rollback due to error processing Exchange ID: {}",
exchange.getExchangeId());
}
{code}
The option documentation says the deferred acknowledgement "allows to rollback
and retry the message in case of an error during routing in Camel". MQTT has no
negative acknowledgement, though. The broker sends an unacknowledged PUBLISH
again only when the client connects again to the same session (MQTT 5.0 section
4.4), and it never has more than the client's Receive Maximum unacknowledged
QoS 1 / QoS 2 messages in flight to it (section 4.9; MQTT 3.1.1 brokers have
their own in-flight window). So with the code on main:
# the failed message is not retried while the connection lasts; with a clean
session ({{cleanStart=true}}, the default) it is never sent again;
# every failed message keeps one place of the receive window. After
{{receiveMaximum}} failed exchanges (65535 by default; a broker may use a
smaller in-flight window, and users lower it to limit the work in flight) the
broker sends the consumer nothing more. The route stays started, nothing is
logged above DEBUG, and the messages wait at the broker until the route is
restarted.
*Fix*: in {{onFailure}}: with {{cleanStart=false}} the consumer disconnects and
connects again (on one scheduled executor per consumer, as the Paho client does
not allow a disconnect from its callback) and subscribes again, so the broker
sends the message again: the documented rollback and retry. The reconnects of
failed exchanges in a row back off, so that a message that always fails does
not make the consumer reconnect in a tight loop: the first waits the client's
minimum automatic reconnect delay (1 s), each further one twice as long, up to
{{maxReconnectDelay}}; a successful exchange resets it, and each such reconnect
is logged at WARN. A reconnect that fails is scheduled again with the next
delay. With a clean session, where the broker never sends the message again,
the failed message is acknowledged, which frees its place; the failure is left
to the Camel error handler as for any other exchange. A consumer that uses a
client set with the {{client}} option is not reconnected (WARN, behaviour as
before). New section "Manual acknowledgements" in the component page, upgrade
note in the 4.24 guide.
*Tests*: new {{PahoMqtt5ManualAcksFailureTest}} (4 tests, the real Paho client
against an embedded Artemis MQTT broker from {{camel-test-infra-artemis}}, as
in camel-paho; Artemis honours the client's Receive Maximum).
{{failedMessagesDoNotStopTheConsumer}} ({{receiveMaximum=2}}; fail-1, fail-2
fail once, ok-3 and ok-4 follow) fails on main: {{Expecting actual: [] to
contain exactly ["ok-3", "ok-4"]}} (nothing reaches the route after the two
failures). {{failedMessageIsDeliveredAgainWithPersistentSession}}
({{cleanStart=false&sessionExpiryInterval=60}}) fails on main: {{Expecting
actual: ["ok-2"] to contain exactly in any order ["fail-1", "ok-2"]}} (fail-1
is never retried). {{reconnectsAfterFailuresInARowBackOff}} (a message that
fails three times, {{maxReconnectDelay=1500}}) checks with the fix that the
attempts are at least 1 s, 1.5 s and 1.5 s apart and that the success resets
the backoff; on main the message is never delivered again ({{Expecting actual:
[] to contain exactly ["fail-3"]}}).
{{acknowledgedMessagesKeepTheReceiveWindowOpen}} passes on main and with the
fix: four successful messages through a window of 2, so the stall comes from
the failures. camel-paho-mqtt5 unit tests: 21 run, 0 failures.
*How it was found*: a TLA+ model of the manual acknowledgement protocol (broker
in-flight window W, redelivery only on reconnect, Camel's onComplete /
onFailure). The liveness property "every published message reaches the route"
is violated with the code on main in 7 steps for N=4, W=2 (two failed messages
fill the window, then stuttering); with the fix it holds (persistent session:
every message is also routed successfully, no message dropped; N=6, W=3, 2
failures per message, 20,263 states). Then the JUnit test above.
*Affected*: main, camel-4.23.x, camel-4.22.x, camel-4.18.x and camel-4.14.x
({{onFailure}} only logs on all of them; read, not run). camel-paho (MQTT
3.1.1, deprecated) has the same {{onFailure}} and the same behaviour (brokers
limit the unacknowledged messages in flight to an MQTT 3.1.1 client too,
Artemis to 100 by default); it is not changed here.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)