Skip to content

CAMEL-24401: Fix permanent temporary replyTo strand in camel-jms - #25531

Open
atiaomar1978-hub wants to merge 1 commit into
apache:mainfrom
atiaomar1978-hub:cursor/CAMEL-24401-jms-replyto-refresh-c587
Open

CAMEL-24401: Fix permanent temporary replyTo strand in camel-jms#25531
atiaomar1978-hub wants to merge 1 commit into
apache:mainfrom
atiaomar1978-hub:cursor/CAMEL-24401-jms-replyto-refresh-c587

Conversation

@atiaomar1978-hub

@atiaomar1978-hub atiaomar1978-hub commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

AI-generated on behalf of atiaomar1978-hub

Fixes CAMEL-24401: camel-jms InOut producers using temporary reply queues could enter a permanent Failed to resolve replyTo destination loop after JMS connection faults.

Root cause

TemporaryQueueReplyManager used a one-shot refreshWanted flag that was cleared before a replacement temporary queue was published (Scenario B), and could remain armed with no consumer left to call the destination resolver (Scenario A).

Fix

  • Replace refreshWanted with a monotonic refresh generation counter and only publish replyTo after a successful queue creation for the handled generation (prevents Scenarios B and C).
  • When a refresh is scheduled, defer listener-container recovery until after the endpoint recovery interval and skip while Spring is already recovering, forcing a resolver call for Scenario A without racing Spring recovery.
  • Add DefaultJmsMessageListenerContainer.recoverReplyDestinationAfterRefresh() as a guarded hook into Spring recovery.

Tests

  • TemporaryQueueReplyManagerRefreshTest — failed create retry, latest generation publish, permanent failure, generation change during resolve
  • JmsTemporaryReplyToRequestReplyIT — consecutive InOut request-reply sanity check

Backport

References

Replace the one-shot refreshWanted flag with a generation counter that is
only acknowledged after a replacement temporary queue is published to replyTo.
When a refresh is scheduled, defer listener-container recovery until after
the endpoint recovery interval and skip while Spring is already recovering,
so a pending refresh cannot outlive the last destination resolve (Scenario A).

Add unit tests for failed refresh retries, concurrent refresh generations,
generation changes during resolve, and a request-reply integration test.

Co-authored-by: Cursor <cursoragent@cursor.com>
@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI Review Summary (Bugbot + Grok)

AI-generated on behalf of atiaomar1978-hub

Both reviews were run against this changeset. Findings below, with status after follow-up commits in this PR.


Bugbot findings

Severity Finding Status
High setReplyTo was called before generation confirmation; a concurrent scheduleRefresh() could publish then delete a queue, or return a deleted queue AddressedsetReplyTo now runs only after refreshGeneration == generationToHandle; stale attempts return null
High Lock-free fast path could return a cached queue after a concurrent arm Addressed — resolver logic is fully under destinationLock; no unsynchronized fast path
Medium Recovery task could stop re-scheduling if container is not running after failed stop/start Partially addressedfinally re-schedules when still pending; Simple-container stop/start path remains less exercised in tests

Grok review (cursor-grok-4.6-high-fast)

Verdict: request changes → several items addressed in latest commit; a few gaps remain for human review.

What looks correct

  • Generation-based refresh (refreshGeneration / publishedGeneration) is the right fix for Scenario B (flag cleared before queue published) and Scenario C (lost update on naive reorder-only fix).
  • Builds on CAMEL-24074 locking without reverting it.
  • Unit tests with failing TemporaryQueueResolver cover the B-shaped retry path well.

Addressed in this PR

  1. Recovery races Spring DMLC — recovery is deferred by recoveryInterval, skips while isRecovering(), and recoverReplyDestinationAfterRefresh() guards with !isRecovering().
  2. Publish orderingreplyTo published only after generation match.
  3. Shutdown — listener stopped first (super.doStop()), then shutdownNow on recovery executor; loop checks isStopping().
  4. Generation discard during resolve — covered by shouldDiscardPublishWhenRefreshGenerationChangesDuringResolve.

Remaining gaps / human review items

  1. Scenario A not fully reproduced in tests — the DMLC invoker-pool late-arm path (CACHE_CONSUMER + collapsed pool) is not covered by unit/IT tests here; the external reproducer lab remains the authoritative proof. Consider follow-up IT or acceptance note in JIRA.
  2. JmsTemporaryReplyToRequestReplyIT — sanity test for consecutive InOut only; Artemis VM restart() is a no-op, so this does not prove post-broker-fault recovery.
  3. Unrelated generated file in diffcore/camel-core-model/.../model.properties appears unrelated to CAMEL-24401 and should be dropped from this PR before merge.
  4. Simple replyToConsumerType — stop/start recovery path for SimpleMessageListenerContainer is untested.
  5. camel-sjms — same defect family noted in JIRA/reproducer; out of scope here but worth a separate ticket.

Style / conventions

  • Production Thread.sleep in recovery loop uses endpoint recoveryInterval (acceptable for background recovery, but worth committer opinion).
  • New public method recoverReplyDestinationAfterRefresh() on DefaultJmsMessageListenerContainer — thin hook, but it is public API on a released type.

Recommendation

Approach is sound for B/C and a reasonable mitigation for A. Before merge:

  • Remove unrelated model.properties change
  • Human reviewer to confirm deferred recovery timing is acceptable vs. always triggering immediately on arm
  • Acknowledge Scenario A validation relies on reporter reproducer + unit coverage of generation protocol

Backport: #25530

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants