[#4583] fix(messaging): SplitTask leaves token store inconsistent after segment split#4587
Open
MateuszNaKodach wants to merge 2 commits into
Open
[#4583] fix(messaging): SplitTask leaves token store inconsistent after segment split#4587MateuszNaKodach wants to merge 2 commits into
MateuszNaKodach wants to merge 2 commits into
Conversation
…er segment split SplitTask.splitAndRelease() called releaseClaim() before deleteToken(), setting owner=NULL so the DELETE WHERE owner=nodeId found 0 rows and threw UnableToClaimTokenException. Fix: remove releaseClaim() entirely and move deleteToken() before initializeSegment(original) so the DELETE runs while the token is still owned. The re-insert already creates the row with owner=NULL, making releaseClaim redundant. JpaTokenStore.deleteToken() now calls em.flush()+em.clear() after the bulk JPQL DELETE so the deleted entity is evicted from the persistence context, preventing NonUniqueObjectException when initializeSegment() persists a new entity with the same PK in the same unit of work. Add Spring Boot integration tests against JDBC and JPA token stores to reproduce and verify the fix end-to-end.
… segment merge MergeTask called releaseClaim after initializeSegment, but initializeSegment always creates tokens with owner=NULL, making the subsequent releaseClaim (UPDATE SET owner=NULL WHERE owner=nodeId) match zero rows and throw UnableToClaimTokenException. Remove the call to fix post-merge reclaim. Integration tests redesigned with UUID-per-test processor names and a buildProcessor() factory, eliminating @DirtiesContext and Spring property wiring while providing natural token store isolation per test.
|
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.




closes #4583
Bug description
Introduced in: commit 185c4332 — "When splitting and merging tokens, update both tokens involved"
Before persistent segment masks were introduced,
SplitTaskonly needed to insert the sibling segment and callreleaseClaim(original)to release ownership. When that commit added support for updating the original segment's mask, it appendeddeleteToken(original)+initializeSegment(original with new mask)after the existingreleaseClaimrather than replacing it:MergeTaskhas the same problem in itsmergeSegments()method: afterinitializeSegment(mergedSegment)creates the merged token withowner = NULL, the code calledreleaseClaim(merged), which again matches zero rows and throwsUnableToClaimTokenException, preventing the coordinator from reclaiming the merged segment.The
InMemoryTokenStore(used in all existing unit tests) has a no-opreleaseClaimand ignores ownership indeleteToken, so both bugs were invisible to the test suite and only surfaced against a real JDBC or JPA token store.What was fixed
SplitTask.splitAndRelease()—releaseClaim()was removed entirely and the operation order corrected:initializeSegment(sibling)— insert sibling withowner = NULLdeleteToken(original)— DELETE whileowner = nodeIdstill holdsinitializeSegment(original with new mask)— re-insert withowner = NULL, ready for reclaimingMergeTask.mergeSegments()—releaseClaim()was removed. After the two source tokens are deleted and the merged token is initialized withowner = NULL, no claim release is needed — the coordinator picks it up on the next cycle.JpaTokenStore.deleteToken()(found thanks to the new integration tests) — A secondary issue specific to JPA: after a JPQL bulk DELETE the Hibernate identity map still holds the deletedTokenEntry, causingNonUniqueObjectExceptionwheninitializeSegmentcallsem.persist()with the same PK in the same persistence context. Fixed by callingem.flush()+em.clear()afterexecuteUpdate()to evict the stale entry.Tests introduced
Unit —
SplitTaskTest(existing class updated):releaseClaimmock setup and verification from existing tests; addedverify(never()).releaseClaim()initializeSegment(sibling)→deleteToken→initializeSegment(original)Unit —
MergeTaskTest(existing class updated):releaseClaimmock setup and verification from existing tests; addedverify(never()).releaseClaim()Integration — new Spring Boot IT suite against real token stores:
It's a cornerstone for better test coverage, providing more valuable end-to-end tests of
PooledStreamingEventProcessor.PooledStreamingEventProcessorTestSuite(added tomessagingtest-jar, no Spring dependency) holds all test logic. It requires subclasses to implement only abuildProcessor()factory method, making it reusable outside Spring contexts. Each test creates its own processor with a UUID-based name for natural token store row isolation — no schema teardown or context reset needed.PooledStreamingEventProcessorSpringTestSuite(inspring-boot-autoconfigure) is a thin Spring subclass that builds the processor from components obtained via@Autowired AxonConfiguration.Two concrete IT classes run the full suite against HSQLDB in-memory:
PooledStreamingJdbcTokenStoreIT— overrides the JPA token store with aJdbcTokenStorebeanPooledStreamingJpaTokenStoreIT— uses full JPA autoconfiguration withddl-auto=create-dropThe suite covers two scenarios against both token stores:
WhenSplittingSegments— callsprocessor().splitSegment(0)end-to-end and asserts both resulting segments become active, directly reproducing the original bugWhenMergingSegments— splits first, then callsprocessor().mergeSegment(0)and asserts the merged segment becomes active, reproducing the symmetricMergeTaskbug