Skip to content

[GLUTEN-12608][CORE] Make ShuffleManagerRouter cache tolerate the executor lifecycle - #12609

Merged
jackylee-ch merged 1 commit into
apache:mainfrom
LuciferYang:fix/shuffle-router-cache-executor-lifecycle
Jul 24, 2026
Merged

[GLUTEN-12608][CORE] Make ShuffleManagerRouter cache tolerate the executor lifecycle#12609
jackylee-ch merged 1 commit into
apache:mainfrom
LuciferYang:fix/shuffle-router-cache-executor-lifecycle

Conversation

@LuciferYang

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

ShuffleManagerRouter's inner Cache assumed each shuffleId is stored once, before any get or remove. That holds on the driver, where registerShuffle runs single-threaded from the DAGScheduler, but not on executors, where the cache is filled lazily and concurrently by task threads through getReader/getWriter. Two problems followed.

store used cache.compute with assert(m == null). When several tasks of the same new shuffleId first touch the cache at once on a multi-core executor, ConcurrentHashMap.compute serializes them and the later threads hit that assertion, so those tasks fail with an AssertionError. A retry succeeds once the cache is populated, but the failures still surface as flaky tasks. This change makes store an idempotent computeIfAbsent and takes the manager by name, so the lookup still runs only on a miss. The has() check in ensureShuffleManagerRegistered is then redundant, so it is removed.

remove used assert(manager != null). Spark broadcasts RemoveShuffle to every executor, so a router that never cached a shuffleId still gets unregisterShuffle and hits that assertion. remove now returns an Option, and unregisterShuffle reports that it removed nothing, matching SortShuffleManager, which returns a boolean instead of throwing.

Cache.get still asserts. Every path that reaches it runs on an executor that already registered the shuffleId (as writer via getWriter, or as reader via getReader, which registers before serving), so it is left unchanged.

How was this patch tested?

Added ShuffleManagerRouterCacheSuite with two tests:

  • unregisterShuffle on a shuffleId this router never cached returns false instead of throwing.
  • 4 threads over 200 iterations first-touching the same new shuffleId produce no errors.

Both fail on the current code (they hit the two assertions) and pass after the fix. The existing GlutenShuffleManagerSuite still passes.

Closes #12608

…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.
Copilot AI review requested due to automatic review settings July 23, 2026 09:56
@github-actions github-actions Bot added the CORE works for Gluten Core label Jul 23, 2026
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Copilot AI 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.

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.store idempotent under concurrent first-touch by switching to computeIfAbsent and accepting the manager lookup by-name.
  • Make Cache.remove miss-tolerant (Option) and update unregisterShuffle to return false on 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.

@jackylee-ch jackylee-ch 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.

👍

@jackylee-ch
jackylee-ch merged commit 8b70147 into apache:main Jul 24, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CORE works for Gluten Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GlutenShuffleManager router cache throws on concurrent first-touch and on unregisterShuffle for uncached shuffle ids

4 participants