Topic event published during a republish is no longer lost - #6834
Open
TIVMOF wants to merge 10 commits into
Open
Topic event published during a republish is no longer lost#6834TIVMOF wants to merge 10 commits into
TIVMOF wants to merge 10 commits into
Conversation
…er lost Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR:
Problem
Client-Java topic subscriptions were non-durable, and
ListenerClassConsumer.register()tears every subscription down before re-registering it. A non-durable subscriber receives only what is published while it is connected, so every event raised inside that window was discarded — while the producer sent itPERSISTENT, paying for persistence a non-durable subscriber cannot collect.An operator republishes at 14:32. A record created by an integration at 14:32:01 lands in the teardown window: the repository publishes the create event, nothing is subscribed, the message is gone. That record never starts its process, resolves its register or posts its document, and is indistinguishable from one the automation legitimately had nothing to do for.
Change
Topics are now opened with
createDurableSubscriberunder a client id derived from the handler's label and the tenant-resolved destination, so the broker holds their messages until the handler reconnects. The id must be stable — a varying one would open an empty subscription on every reload and strand the previous one's backlog — which is what the derivation and its test pin down.Queues are deliberately untouched: a queue already retains messages for an absent consumer.
ActiveMQConnectionArtifactsFactorygains a client-id overload, since JMS forbids setting a client id on a started connection and the existingcreateConnectionstarts it.Cleanup is the broker's job
A durable subscription outlives its subscriber by design, so nothing reclaims one whose handler was deleted rather than reloaded. That cannot be fixed in the consumer:
JavaLoader.applyGenerationbuilds its unload set from the removed and the replaced FQNs, soonClassUnloadedfires on a republish exactly as on a deletion — unsubscribing there would destroy the subscription this change exists to preserve. A class deleted while the server was down is never reported at all, since the previous generation starts empty.So the reclaim is declarative:
offlineDurableSubscriberTimeout(7 days) inMessagingConfig, generous enough that no restart or republish can expire a subscription that is about to reconnect.Note on the restart case
The broker is embedded (
vm://localhost), so during a JVM restart there is no producer either — the publishers are generated repositories in the same JVM. Nothing is lost at restart because nothing is being sent. The republish window, where the JVM stays up and other writers keep going, is the one that matters.Trade-off
A topic's messages now accumulate in the broker's store (SystemDB) while a handler is down, instead of being discarded. That is the point of the change, but it means a handler left broken or undeployed grows the store until the sweep reclaims its subscription. Documented in
engine-java/CLAUDE.mdalongside the stable-id requirement and the unload-hook reasoning.Testing
Four new tests in
ListenerClassConsumerDurabilityTest— there was no topic coverage at all before, so this path was entirely unverified. Verified red-first: with the change stripped, two fail witha durable subscription needs the connection to carry a client id ==> expected: not <null>.Green: engine-java 98/98, engine-listeners 51/51 (the JavaScript listener path is unaffected). Two existing tests needed their
createConnectionstub widened to the two-arg call — mock maintenance from the signature change, no assertion altered.🤖 Generated with Claude Code