shashank created CAMEL-25679:
--------------------------------

             Summary: camel-mongodb - the tailable and change streams consumers 
stop consuming for good (route still started) when the server is unreachable 
for longer than the server selection timeout
                 Key: CAMEL-25679
                 URL: https://issues.apache.org/jira/browse/CAMEL-25679
             Project: Camel
          Issue Type: Bug
          Components: camel-mongodb
            Reporter: shashank
            Assignee: shashank


The tailable cursor consumer ({{consumerType=tailable}}, the default) and the 
change streams consumer ({{consumerType=changeStreams}}) read from a cursor on 
one thread, {{MongoAbstractConsumerThread.run()}}. When reading fails (for 
example the connection to the server is lost), the loop logs a WARN ("Will try 
again on next poll"), closes the cursor, waits {{cursorRegenerationDelay}} 
(1000 ms by default) and opens a new cursor ({{initializeCursor()}}).

At main cd2ce899764e the new cursor is opened at line 130, after the {{try}} of 
the loop (101-115). While the server is still unreachable, opening the cursor 
fails: the driver gives up after its server selection timeout (30 seconds by 
default) with {{MongoTimeoutException: Timed out while waiting for a server}}. 
The exception leaves {{run()}}, the consumer thread ends, and:
* the route stays started and the consumer never reads again, also long after 
the server is back; only a restart of the route helps;
* the only trace is the uncaught exception printed on stderr by the thread 
({{Exception in thread "Camel (camel-1) thread #1 - mongodb://..." 
com.mongodb.MongoTimeoutException ...}}), not through the logging framework.

Trigger: default options, a MongoDB server (or replica set) that cannot be 
reached for longer than about 31 seconds (server selection timeout plus the 
regeneration delay): a restart, a failover that takes long, a network outage, 
maintenance. A long-running tailing or change stream consumer will see such an 
outage.

h3. Reproduction

New {{MongoDbConsumerCursorRegenerationTest}} with Mockito mocks of the driver 
objects (no server): the cursor of the start fails its read 
({{MongoSocketReadException}}), the first new cursor cannot be opened 
({{MongoTimeoutException}}), the next one returns a document. On main:
* tailable consumer: {{mock://result Received message count. Expected: <1> but 
was: <0>}}, and the test output shows {{Exception in thread "Camel (camel-1) 
thread #1 - mongodb://myDb" com.mongodb.MongoTimeoutException: Timed out while 
waiting for a server}};
* change streams consumer: the same.
* A third test (the tailing thread run directly, stopped while it opens a new 
cursor) pins a related small leak of the same lines: a cursor opened while the 
consumer is being stopped was neither read nor closed on main ({{Wanted but not 
invoked: mongoCursor.close()}}).

h3. Proposed fix

Open the new cursor at the start of the next iteration of the loop, inside its 
{{try}}: a failure is logged like a failed read ("Exception from consuming from 
MongoDB caused by ... Will try again on next poll.") and tried again after 
{{cursorRegenerationDelay}}, until the server is back or the consumer is 
stopped. A cursor opened while the consumer was being stopped is closed instead 
of read (stop() could not close it). The {{cursor}} field becomes volatile, as 
{{stop()}} reads it from another thread.

While the server is down there is one WARN with stack trace per attempt, every 
server selection timeout plus {{cursorRegenerationDelay}} (about 31 seconds by 
default), like the WARN that main already logs for every failed read. No option 
or default changes, so no upgrade guide entry.

camel-mongodb unit tests with the fix: 21 run, 0 failures, 0 skipped. Not run: 
the module's integration tests (they need a MongoDB container), a real outage.

How it was found: reading the consumers' loop during a sweep of reconnect 
paths; the test confirmed it.

Not changed: the tailable consumer still ends quietly when the driver throws 
{{IllegalStateException}} from {{hasNext()}} (meant for a cursor closed by 
{{stop()}}); the change streams consumer logs a failed read at DEBUG only 
(CAMEL-16025).

Affected: main (cd2ce899764e; tested). camel-4.23.x (line 130), camel-4.22.x, 
camel-4.18.x and camel-4.14.x (line 94) open the new cursor outside the {{try}} 
the same way (read, not run).

Duplicate check (2026-10-15): JIRA text "MongoAbstractConsumerThread" 
(CAMEL-16025, CAMEL-16024, CAMEL-25024, CAMEL-25025, CAMEL-17121 retry logic 
consolidation; none about the regeneration), "MongoTimeoutException" 
(CAMEL-24257, camel export; CAMEL-12142, tests), component camel-mongodb with 
"tailable cursor" (CAMEL-11977 stop on shutdown, 2017); GitHub pull requests 
"MongoAbstractConsumerThread" (#26961, failure handling of the route, did not 
touch the regeneration), "mongodb tailable cursor", "mongodb change streams 
reconnect" (nothing related).

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to