[GLUTEN-12608][CORE] Make ShuffleManagerRouter cache tolerate the executor lifecycle - #12609
Merged
jackylee-ch merged 1 commit intoJul 24, 2026
Conversation
…cutor lifecycle ShuffleManagerRouter's inner Cache assumed a single-coordinator lifecycle (each shuffleId stored exactly once, before any get/remove). That holds on the driver but not on executors, where the cache is populated lazily and concurrently by task threads. - store: concurrent first-touch of the same new shuffleId on a multi-core executor made the losing threads trip assert(m == null) under ConcurrentHashMap.compute, failing those tasks with an AssertionError. Switch to an idempotent computeIfAbsent with a by-name manager so the lookup still runs only on a miss, and drop the now-redundant has() guard. - remove / unregisterShuffle: Spark broadcasts RemoveShuffle to every executor, so a router that never cached a shuffleId still receives unregisterShuffle and tripped assert(manager != null). Return an Option and report that this router removed nothing, matching SortShuffleManager which returns a boolean rather than throwing. Add ShuffleManagerRouterCacheSuite covering both paths.
|
Run Gluten Clickhouse CI on x86 |
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request fixes two executor-side lifecycle assumptions in ShuffleManagerRouter’s per-executor cache that caused flaky task failures and noisy error logs: concurrent “first-touch” registration of a shuffleId and unregisterShuffle calls for shuffleIds that were never cached on a given executor.
Changes:
- Make
Cache.storeidempotent under concurrent first-touch by switching tocomputeIfAbsentand accepting the manager lookup by-name. - Make
Cache.removemiss-tolerant (Option) and updateunregisterShuffleto returnfalseon cache misses instead of asserting. - Add a focused unit test suite reproducing both failure modes and validating the new behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| gluten-core/src/main/scala/org/apache/spark/shuffle/ShuffleManagerRouter.scala | Makes the router cache tolerant of executor concurrency and “remove on uncached shuffleId” lifecycle by using computeIfAbsent and miss-safe removal. |
| gluten-core/src/test/scala/org/apache/spark/shuffle/ShuffleManagerRouterCacheSuite.scala | Adds regression tests for concurrent first-touch and unregisterShuffle cache-miss behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
zhztheplayer
approved these changes
Jul 23, 2026
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.
What changes were proposed in this pull request?
ShuffleManagerRouter's innerCacheassumed each shuffleId is stored once, before anygetorremove. That holds on the driver, whereregisterShuffleruns single-threaded from the DAGScheduler, but not on executors, where the cache is filled lazily and concurrently by task threads throughgetReader/getWriter. Two problems followed.storeusedcache.computewithassert(m == null). When several tasks of the same new shuffleId first touch the cache at once on a multi-core executor,ConcurrentHashMap.computeserializes them and the later threads hit that assertion, so those tasks fail with anAssertionError. A retry succeeds once the cache is populated, but the failures still surface as flaky tasks. This change makesstorean idempotentcomputeIfAbsentand takes the manager by name, so the lookup still runs only on a miss. Thehas()check inensureShuffleManagerRegisteredis then redundant, so it is removed.removeusedassert(manager != null). Spark broadcastsRemoveShuffleto every executor, so a router that never cached a shuffleId still getsunregisterShuffleand hits that assertion.removenow returns anOption, andunregisterShufflereports that it removed nothing, matchingSortShuffleManager, which returns a boolean instead of throwing.Cache.getstill asserts. Every path that reaches it runs on an executor that already registered the shuffleId (as writer viagetWriter, or as reader viagetReader, which registers before serving), so it is left unchanged.How was this patch tested?
Added
ShuffleManagerRouterCacheSuitewith two tests:unregisterShuffleon a shuffleId this router never cached returnsfalseinstead of throwing.Both fail on the current code (they hit the two assertions) and pass after the fix. The existing
GlutenShuffleManagerSuitestill passes.Closes #12608