Skip to content

CAMEL-25843: camel-hazelcast - the reliable topic consumer goes on after it lost messages - #28264

Merged
davsclaus merged 3 commits into
apache:mainfrom
allthingssecurity:camel-hazelcast-reliable-topic-consumer-keeps-listening
Oct 22, 2026
Merged

davsclaus merged 3 commits into
apache:mainfrom
allthingssecurity:camel-hazelcast-reliable-topic-consumer-keeps-listening

Conversation

@allthingssecurity

@allthingssecurity allthingssecurity commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

CAMEL-25843

With reliable=true the hazelcast-topic consumer listened with a plain MessageListener. A reliable topic keeps its messages in a ringbuffer of limited capacity; when a listener falls behind by more than that, Hazelcast removes a listener that is not loss tolerant for good (MessageRunner.accept cancels it; ReliableMessageListenerAdapter.isLossTolerant() is false). After one burst that a slow route could not keep up with, the route stayed started and received nothing again, and Camel logged nothing.

Changes:

  • New CamelReliableMessageListener (a ReliableMessageListener, loss tolerant): logs a WARN with the number of messages lost (overwritten or expired in the ringbuffer) and goes on with the oldest message still in the ringbuffer; starts with the messages published from then on, as before.
  • When Hazelcast cancels the listener for another reason (MessageRunner.cancel() calls onCancel(): an error reading the ringbuffer, such as a destroyed topic), the listener reports it through the consumer's exception handler (WARN by default) while the consumer is started. A normal stop removes the listener while the consumer is stopping, which reports nothing. A cancel while the Hazelcast instance is shutting down (its lifecycle service is no longer running; the runner fails with HazelcastInstanceNotActiveException) or while the CamelContext is stopping is logged at DEBUG only: Hazelcast's JVM shutdown hook runs at the same time as Camel's, so on an ordinary shutdown the member can stop while the route is still started (review follow-up 9224021).
  • HazelcastTopicConsumer uses it for a reliable topic. The plain topic is unchanged.

No option changed. Users can notice the change (a consumer that fell behind used to stop receiving; now it skips the lost messages and goes on), so there is an entry in the 4.24 upgrade guide, next to the other camel-hazelcast entries, and a paragraph on the hazelcast-topic page about reliable=true (catalog copy updated).

Tests:

  • New HazelcastReliableTopicConsumerLossTest (embedded member, ringbuffer capacity 10): the route is busy with a first message while 30 more are published, then one more is published. Without the change it is never received (Expected: <1> but was: <0>); with the change it is, and a WARN reports the messages that were overwritten (about 20; the exact number depends on how far the ringbuffer has moved when the listener reads again).
  • listenerCancelledByHazelcastIsReported destroys the topic while the route runs: Hazelcast cancels the listener and the consumer's exception handler gets one report. listenerRemovedWhenTheConsumerStopsIsNotReported stops the route: no report. Without onCancel() nothing reports the cancelled listener, so the first one cannot pass; the second is a control.
  • listenerCancelledWhenTheMemberShutsDownIsNotReported shuts the member down while the route is started and checks for 1 s that nothing is reported. Without the shutdown check it fails (the exception handler gets Hazelcast cancelled the listener of the reliable topic news).
  • camel-hazelcast unit tests with the change: 250 run, 0 failures, 1 error (HazelcastUserConfigSerializationFilterWarningTest.warnsForUserClientConfigWithoutFilter: a client that cannot reach a cluster here; it errors the same way without the change).

How it was found: variant analysis of CAMEL-25815 (the server side ends a consumer's subscription and the consumer never subscribes again), then the JUnit test.

What was not run: a Hazelcast client (only an embedded member); the full root build.

Target

  • I checked that the commit is targeting the correct branch (Camel 4 uses the main branch)

Tracking

  • If this is a large change, bug fix, or code improvement, I checked there is a JIRA issue filed for the change (usually before you start working on it): CAMEL-25843.

Apache Camel coding standards and style

  • I checked that each commit in the pull request has a meaningful subject line and body.
  • I have run mvn clean install -DskipTests locally from root folder and I have committed all auto-generated changes.
    (I built and tested camel-hazelcast. No option and no component page changed. I did not run the full root build.)

AI-assisted contributions

  • If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., Co-authored-by trailers) and the PR description identifies the AI tool used.
    This PR was prepared with Claude Code (Claude Opus 5.5). The commit carries a Co-Authored-By trailer.

Claude Code on behalf of allthingssecurity

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Shashank,

I checked this against Hazelcast 5.4.0 (the version we use). ReliableTopicProxy and ClientReliableTopicProxy use the listener as is when it implements ReliableMessageListener, and wrap a plain MessageListener in ReliableMessageListenerAdapter, whose isLossTolerant() is false. MessageRunner.accept then cancels it on the first gap. For a loss tolerant listener Hazelcast logs the gap only at FINEST (MessageRunner.isLossTolerable), so the WARN from storeSequence is the only thing a user sees. ReadManyOperation clamps a stale sequence to the head, so the runner reads on and no StaleSequenceException is thrown, on a member or a client. retrieveInitialSequence() = -1 and isTerminal() = false match what the adapter did. The code is good; one thing is needed before merge (point 1).

  1. Blocking: this changes what users see. Before, a consumer that fell behind stopped receiving for good. Now it skips the lost messages and goes on. Please add a short note to camel-4x-upgrade-guide-4_24.adoc (it already has several camel-hazelcast entries), and a sentence on the hazelcast-topic page next to the reliable option. Today the page does not say what happens when a reliable listener falls behind.
  2. Optional: see the inline comment on onCancel(). Hazelcast can still cancel the listener for other reasons, and then the route also stays started without receiving anything.
  3. Optional: the WARN only says "overwritten". Messages can also be lost because they expired (ringbuffer time-to-live), as the class javadoc says.

Claude Code on behalf of davsclaus. This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

allthingssecurity and others added 2 commits October 22, 2026 11:31
…ter it lost messages

A reliable topic keeps its messages in a ringbuffer. When a listener falls
behind by more than the ringbuffer holds (its capacity, 10000 by default,
or its time to live), the messages it did not read yet are overwritten,
and Hazelcast removes a listener that is not loss tolerant for good. The
consumer registered a plain MessageListener, which Hazelcast treats as not
loss tolerant: after one burst that a slow route could not keep up with,
the route stayed started and received nothing again, and nothing was
logged by Camel.

The reliable topic consumer now registers a ReliableMessageListener that
is loss tolerant: it logs a WARN with the number of messages lost and goes
on with the oldest message still in the ringbuffer. It starts with the
messages published from then on, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… topic consumer, and report a listener that Hazelcast cancels

Per review:
- Upgrade guide 4.24: a reliable topic consumer that falls behind now skips the lost messages and goes on (next
  to the other camel-hazelcast entries); a paragraph on the hazelcast-topic page about reliable=true (catalog
  copy updated).
- CamelReliableMessageListener.onCancel(): when Hazelcast cancels the listener for another reason (an error
  reading the ringbuffer, a destroyed topic, an instance no longer active) while the consumer is started, report
  it through the consumer's exception handler; a normal stop reports nothing.
- The WARN says the lost messages were overwritten or expired.
- Tests: a destroyed topic is reported once; a route stop is not reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@allthingssecurity
allthingssecurity force-pushed the camel-hazelcast-reliable-topic-consumer-keeps-listening branch from 7a9738d to a6de186 Compare October 22, 2026 06:35
@allthingssecurity

Copy link
Copy Markdown
Contributor Author

Thanks Claus. I rebased on main (the 4.24 guide had moved on; the existing commit is unchanged) and pushed a6de186 on top:

  1. (blocking) New upgrade-guide entry camel-hazelcast - the reliable topic consumer goes on after it lost messages, right after camel-hazelcast - the transacted SEDA consumer .... It says what users see now: the consumer skips the lost messages (overwritten or expired), logs a WARN with their number and goes on. When Hazelcast cancels the listener for another reason, the consumer reports it to its exception handler. The hazelcast-topic page has a paragraph on reliable=true in the consumer section, catalog copy included. The options table is generated, so the text is not in the option description, and no metadata changed.
  2. onCancel(): see inline. Reported through the consumer's exception handler while the consumer is started; a normal stop reports nothing. Two new tests.
  3. WARN wording: "overwritten or expired" (see inline).

camel-hazelcast: 249 tests, 0 failures, 1 error (HazelcastUserConfigSerializationFilterWarningTest, a client that cannot reach a cluster here; it errors the same way on main). The branch merges cleanly with main and with the other 4.24 guide entries of this batch (#28262).

Claude Code on behalf of allthingssecurity

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid fix. The ReliableMessageListener implementation is correct, the key behaviors are well-covered by tests, and all three of @davsclaus's review points are addressed.

Implementation review:

  • isLossTolerant() = true directly fixes the root cause: Hazelcast's ReliableMessageListenerAdapter wraps a plain MessageListener with isLossTolerant() false, which causes MessageRunner.accept() to cancel the listener on the first gap. The new listener avoids this.
  • storeSequence() gap arithmetic is correct per the Hazelcast contract (sequences are monotonically increasing; storeSequence is called in order by a single thread per listener — lastSequence does not need to be volatile).
  • onCancel() guard via isStarted() is correct: BaseService.stop() transitions status to STOPPING before calling doStop(), which calls removeMessageListener(), which triggers onCancel(). At that point isStarted() returns false (STOPPING ≠ STARTED). No false alarm on normal route stop.
  • isTerminal() = false is correct: CamelListener.sendExchange() wraps processor.process() and routes exceptions to the exception handler — route failures never reach Hazelcast as isTerminal inputs.

Tests: consumerGoesOnAfterFallingBehindTheRingbuffer simulates the exact production failure (capacity 10 ringbuffer, 30-message burst, deterministic blocking via CountDownLatch). The other two tests cover the onCancel behavior cleanly. Pre-fix behavior: without CamelReliableMessageListener, the mock would never receive "after" (Hazelcast removes the plain listener on the gap) and listenerCancelledByHazelcastIsReported would timeout (no onCancel override). Tests correctly validate the fix.

LGTM.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Shashank, all three points are addressed:

  1. The upgrade guide entry in 4_24 and the paragraph on the hazelcast-topic page (with the catalog copy) read well.
  2. onCancel(): I checked it against Hazelcast 5.4.0. removeMessageListener cancels the runner in the calling thread, so the isStarted() check skips the normal stop. A destroyed topic is reported once, because the runner has already left runnersMap when the route stops later. The two tests cover both cases.
  3. The WARN and the javadoc now say "overwritten or expired".

One optional nit inline about shutdown noise. Approving, to merge once CI is green.

Claude Code on behalf of @davsclaus.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

…r that Hazelcast cancels while Hazelcast or Camel shuts down

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Shashank, the shutdown nit is addressed. onCancel() now logs at DEBUG when the instance's lifecycle service is not running or the CamelContext is stopping, and a destroyed topic is still reported. I checked Hazelcast 5.4.0: HazelcastInstanceProxy and HazelcastClientProxy return a TerminatedLifecycleService from getLifecycleService() once the instance is gone, so the check cannot throw. The new test and the reworded upgrade guide entry look good.

Approving, to merge once CI is green.

Claude Code on behalf of @davsclaus. This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-hazelcast
  • docs

🔁 1 test passed only after a retry on JDK 25 (1 retried attempt)

Recovered flaky tests on JDK 25 (1)
Module Test Failed attempts First failure
components/camel-hazelcast HazelcastReliableTopicConsumerLossTest.listenerCancelledByHazelcastIsReported 1 Condition with Lambda expression in org.apache.camel.component.hazelcast.HazelcastReliableTopicConsumerLossTest was not fulfilled within 10 seconds.

ℹ️ These tests did not fail the build. Surefire retried them and they passed.
Retries are enabled project-wide by surefire.rerunFailingTestsCount in parent/pom.xml.


🔬 Scalpel shadow comparison — Scalpel: 8 of 707 tested, 25 compile-only — current: 8 all tested

Maveniverse Scalpel detected 8 affected modules (current approach: 8).

Skip-tests mode would test 8 modules (3 direct + 7 downstream), skip tests for 25 (generated code, meta-modules)

Modules Scalpel would test (8)
  • camel-hazelcast ← components/camel-hazelcast/src/main/docs/hazelcast-topic-component.adoc, components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/listener/CamelReliableMessageListener.java, components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/topic/HazelcastTopicConsumer.java, components/camel-hazelcast/src/test/java/org/apache/camel/component/hazelcast/HazelcastReliableTopicConsumerLossTest.java
  • camel-jbang-mcp ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-mcp ← downstream of org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-tui ← downstream of org.apache.camel:camel-catalog
  • camel-jbang-plugin-validate ← downstream of org.apache.camel:camel-yaml-dsl-validator
  • camel-launcher-container ← downstream of org.apache.camel:camel-launcher
  • camel-yaml-dsl-validator ← downstream of org.apache.camel:camel-catalog
  • camel-yaml-dsl-validator-maven-plugin ← downstream of org.apache.camel:camel-yaml-dsl-validator
Modules with tests skipped (25)
  • apache-camel
  • camel-allcomponents
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (35 modules, 4m 23s total)

Total reactor time: 4m 23s

Module Duration Status
Camel :: JBang :: Plugin :: TUI 54.3s SUCCESS
Camel :: Launcher 27.1s SUCCESS
Camel :: JBang :: Plugin :: Validate 24.5s SUCCESS
Camel :: YAML DSL 19.8s SUCCESS
Camel :: JBang :: MCP 19.1s SUCCESS
Camel :: YAML DSL :: Validator 16.8s SUCCESS
Camel :: Catalog :: Camel Catalog 14.9s SUCCESS
Camel :: Component DSL 13.3s SUCCESS
Camel :: Kamelet Main 12.1s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 8.6s SUCCESS
Camel :: Docs 8.1s SUCCESS
Camel :: Catalog :: Camel Report Maven Plugin 6.1s SUCCESS
Camel :: YAML DSL :: Deserializers 5.1s SUCCESS
Camel :: Catalog :: Camel Route Parser 4.8s SUCCESS
Camel :: JBang :: Plugin :: Testing 4.7s SUCCESS
Camel :: All Components Sync point 3.8s SUCCESS
Camel :: Catalog :: Suggest (deprecated) 3.4s SUCCESS
Camel :: YAML DSL :: Validator Maven Plugin 2.7s SUCCESS
Camel :: YAML DSL :: Maven Plugins 2.4s SUCCESS
Camel :: Catalog :: Maven 2.0s SUCCESS
Camel :: Assembly 1.6s SUCCESS
Camel :: Coverage 1.3s SUCCESS
Camel :: JBang :: Plugin :: Edit 1.1s SUCCESS
Camel :: Catalog :: Dummy Component 1.1s SUCCESS
Camel :: JBang :: Plugin :: Generate 0.8s SUCCESS
Camel :: Catalog :: Console 0.8s SUCCESS
Camel :: Endpoint DSL :: Support 0.7s SUCCESS
Camel :: JBang :: Integration tests 0.7s SUCCESS
Camel :: JBang :: Main 0.6s SUCCESS
Camel :: Launcher :: Container 0.5s SUCCESS
Camel :: JBang :: Plugin :: MCP 0.4s SUCCESS
Camel :: Endpoint DSL n/a
Camel :: HazelCast n/a
Camel :: Integration Tests n/a
Camel :: JBang :: Core n/a

Top 20 slowest modules:

  • Camel :: JBang :: Plugin :: TUI (54.3s)
  • Camel :: Launcher (27.1s)
  • Camel :: JBang :: Plugin :: Validate (24.5s)
  • Camel :: YAML DSL (19.8s)
  • Camel :: JBang :: MCP (19.1s)
  • Camel :: YAML DSL :: Validator (16.8s)
  • Camel :: Catalog :: Camel Catalog (14.9s)
  • Camel :: Component DSL (13.3s)
  • Camel :: Kamelet Main (12.1s)
  • Camel :: JBang :: Plugin :: Kubernetes (8.6s)
  • Camel :: Docs (8.1s)
  • Camel :: Catalog :: Camel Report Maven Plugin (6.1s)
  • Camel :: YAML DSL :: Deserializers (5.1s)
  • Camel :: Catalog :: Camel Route Parser (4.8s)
  • Camel :: JBang :: Plugin :: Testing (4.7s)
  • Camel :: All Components Sync point (3.8s)
  • Camel :: Catalog :: Suggest (deprecated) (3.4s)
  • Camel :: YAML DSL :: Validator Maven Plugin (2.7s)
  • Camel :: YAML DSL :: Maven Plugins (2.4s)
  • Camel :: Catalog :: Maven (2.0s)

⚙️ View full build and test results

@davsclaus davsclaus added this to the 4.24.0 milestone Oct 22, 2026
@davsclaus davsclaus added the bug Something isn't working label Oct 22, 2026
@davsclaus
davsclaus merged commit 8e939dc into apache:main Oct 22, 2026
7 checks passed
@allthingssecurity
allthingssecurity deleted the camel-hazelcast-reliable-topic-consumer-keeps-listening branch October 23, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants