You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "shashank (Jira)" <ji...@apache.org> on 2026/10/14 12:05:00 UTC
[jira] [Created] (CAMEL-25655) camel-zookeeper - after the ZooKeeper session expired the consumer waits for ever without any Camel log line, and the producer fails every message, until the route is restarted
shashank created CAMEL-25655:
--------------------------------
Summary: camel-zookeeper - after the ZooKeeper session expired the consumer waits for ever without any Camel log line, and the producer fails every message, until the route is restarted
Key: CAMEL-25655
URL: https://issues.apache.org/jira/browse/CAMEL-25655
Project: Camel
Issue Type: Bug
Components: camel-zookeeper
Reporter: shashank
Assignee: shashank
A ZooKeeper session expires when the client cannot reach the ensemble for longer than the session timeout (the component's {{timeout}} option, 5000 ms by default): a network partition, a long GC pause, a suspended VM, an ensemble that loses its quorum for a few seconds. The server then expires the session; when the client reaches it again it is told so, the {{ZooKeeper}} handle is closed for good (state {{CLOSED}}), and every watcher of the handle gets an event of type {{None}} with state {{Expired}}. A new handle is needed.
Lines at main 1de5ec5135f8:
* {{FutureEventDrivenOperation.process}} (operations, 41-60 in the file) releases the waiting thread only for one of its awaited event types ({{NodeDataChanged}}/{{NodeDeleted}}, {{NodeCreated}}/{{NodeDeleted}}, {{NodeChildrenChanged}}); {{get()}} waits on the latch without a timeout. The {{Expired}} event is ignored.
* {{ConnectionHolder.getZooKeeper}} returns the cached handle whenever it is not null, also when it is closed; nothing in the module creates a new one while the route runs.
Consequences:
* Consumer: its operations thread waits in {{DataChangedOperation}} / {{ExistenceChangedOperation}} / {{ChildrenChangedOperation}} for an event that can no longer come. The ZooKeeper client logs one WARN ("Unable to reconnect to ZooKeeper service, session 0x... has expired"); after that the route stays started, consumes nothing, and Camel logs nothing (the {{Expired}} event is only logged at DEBUG). This hits the configuration for watching a node continuously: {{repeat=true}}.
* The same wait for ever when the expiry is noticed while no watch of the consumer is registered, for example while the operations thread routes an exchange: the next watch request on the closed handle is answered with {{SESSIONEXPIRED}} to the request's callback only ({{ClientCnxn.conLossPacket}}), the watch is not registered and no {{Expired}} event reaches it - and the callbacks of the three operations are empty. The longer the route takes, the more likely this is.
* Producer: every message fails with {{KeeperException.SessionExpiredException}} until the route is restarted.
A route stop / start creates a new consumer and the endpoint's connection manager a new handle ({{doStop}} closes it), so a route restart does recover; that is the only way out on main.
h3. Reproduction
New {{ZooKeeperConsumerSessionExpiryTest}} with a ZooKeeper server in the test JVM ({{ZooKeeperServer}} + {{NIOServerCnxnFactory}} from the zookeeper jar). The session of the Camel handle is expired the usual way: a second client opens the same session (id and password) and closes it. On main:
* consumer ({{repeat=true&backoff=200}}): the node is updated after the expiry; {{the consumer did not consume the update made after its session had expired ==> expected: <true> but was: <false> within 20 seconds}};
* the same, with the expiry while the route's processor holds the operations thread: {{the consumer did not consume the update made after its session had expired during an exchange ...}};
* consumer with {{repeat=false}}: {{the consumer did not read the data again after its session had expired ==> expected: <2> but was: <1> within 20 seconds}} (see the behaviour after the fix below);
* producer: a request after the expiry: {{the producer failed after its session had expired ==> expected: <null> but was: <org.apache.zookeeper.KeeperException$SessionExpiredException: KeeperErrorCode = Session expired for /camel-session-expiry>}}.
With the fix the module log shows the event reaching the operation ("The ZooKeeper session expired while waiting for an event on node ..."), the failed watch request ("Could not watch node ...") and the new session ("The ZooKeeper session 0x... can no longer be used (CLOSED). Creating a new session.").
h3. Proposed fix
* {{FutureEventDrivenOperation}}: on ({{None}}, {{Expired}}) stop waiting and fail with an {{ExecutionException}} caused by {{SessionExpiredException}}. The callbacks of the watch requests of the three operations fail the operation the same way when the result code is not {{OK}} ({{NONODE}} is the normal result of the exists watch and is not a failure there). The consumer hands the failure to its exception handler (one WARN per expiry) and starts the sequence of operations again after the configured {{backoff}}, as it does for any failed operation.
* {{ConnectionHolder.getZooKeeper}}: when the cached handle is no longer alive ({{CLOSED}} or {{AUTH_FAILED}}), close it and create a new handle; each handle counts down its own connection latch.
* {{ZooKeeperConsumer}}: take the connection again before the sequence is started again, and do not queue a copy of the failed operation in addition to the new sequence (on main every restart left that extra copy in the queue; after an expiry it would wait on the closed handle).
* {{ZooKeeperProducer}}: take the connection for each message (a cheap check when the handle is alive).
Behaviour after the fix: the WARN is "The ZooKeeper session expired while waiting for an event on node ...", "Could not watch node ..." when the expiry was noticed before the watch request, or "All operations exhausted without one result" (no cause) for a consumer of a node that does not exist yet ({{AnyOfOperations}} hides the cause). The restarted sequence reads the node again, so after every expiry the consumer emits the node's current data once more, also when it did not change and also with {{repeat=false}}; changes made during the outage are seen only as the latest value.
No option or default changes, no upgrade guide entry. The new test needs {{metrics-core}} and {{snappy-java}} with test scope (zookeeper declares them {{provided}}; the embedded server loads them); the {{snappy-java}} version is a new property in the parent pom.
camel-zookeeper unit tests with the fix: 10 run, 0 failures, 0 skipped. Not run: the module's integration tests (they need a container), an ensemble of several servers.
How it was found: a sweep of the reconnect code of the components, then a small TLA+ model (handle alive / closed, the operations thread waiting / in backoff, expiries, updates). For main it reaches the thread waiting on a closed handle after one expiry, and a run that never watches again; with the fix both properties hold for two expiries (27 states). The model is a sketch; the tests are the evidence.
Not changed: a stopped and started consumer instance (only reachable through a route policy or user code, not through a route restart) still adds no operations, because {{shuttingDown}} is never reset; the thread that creates a new handle waits for its first event without a timeout, as at start (a producer message sent while the ensemble is still unreachable after an expiry waits instead of failing at once).
Affected: main (1de5ec5135f8; tested). camel-4.23.x, camel-4.22.x, camel-4.18.x and camel-4.14.x have the same {{FutureEventDrivenOperation.process}}, {{ConnectionHolder.getZooKeeper}} and empty watch callbacks (read, not run).
Duplicate check (2026-10-14): JIRA text "zookeeper expired" (CAMEL-8208, route policy, 2015), component camel-zookeeper recent issues (cluster view and master only); GitHub pull requests "ZooKeeperConsumer", "zookeeper session expired", "camel-zookeeper consumer" (#25164 moved the backoff to BackgroundTask; nothing about expiry).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)