Skip to content

[GLUTEN-12655][CORE] Reset the component graph after ComponentSuite - #12658

Merged
zhztheplayer merged 3 commits into
apache:mainfrom
LuciferYang:fix/component-suite-graph-leak
Jul 31, 2026
Merged

[GLUTEN-12655][CORE] Reset the component graph after ComponentSuite#12658
zhztheplayer merged 3 commits into
apache:mainfrom
LuciferYang:fix/component-suite-graph-leak

Conversation

@LuciferYang

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

ComponentSuite registers dummy components into the JVM-global component graph, including a deliberate dependency cycle, and never removes them. Any later suite in the same JVM that calls Component.sorted() then fails with Cycle detected in the component graph: B, D, C, naming components unrelated to the failing test. Nothing in gluten-core calls sorted() after ComponentSuite today, but ten call sites in main sources reach it, so a suite that boots a SparkContext with the Gluten plugin fails as soon as it is ordered after ComponentSuite.

Component's graph and the allComponentsLoaded discovery latch are object-level state with no reset, so a suite that registers components cannot clean up after itself. This adds a testing-only reset that empties the graph, clears each component's registration flag so it can register again, and re-arms the latch. ComponentSuite#afterAll calls it.

ensureRegistered now rolls its flag back when graph.add throws. A component that never entered the graph is invisible to Registry#clear, so without the rollback its flag stays set and it can never register again. The rollback covers only graph.add, not dependencies(): a component that is already in the graph would then fail its next registration with a misleading "UID already registered". resetRegisteredForTesting is final, since nine of the eleven in-repo Component implementations sit in this package and could otherwise override it to a no-op and silently defeat the clear.

Four values survive the reset, and the clearAllForTesting scaladoc now says so: BackendsApiManager.backend, GlutenCostModel.costModelRegistry, the graphCache inside Transition.factory, and the per-vertex TransitionGraph.Vertex.initialized flags all keep what they computed from the pre-clear component set. Rediscovery also constructs fresh instances, so one of those values can end up holding an instance that is no longer the one in the graph. The latch line carries a comment explaining it serves the backend modules, whose classpath carries component files, so it does not read as dead code here.

How was this patch tested?

Added ComponentGraphResetSuite with three tests, each covering one part of the reset:

  • register a cycle, reset, assert the graph is empty;
  • register a component, reset, register the same instance again, assert it is back in the graph. This fails without the registration-flag reset;
  • run ComponentSuite in-process via new ComponentSuite().run(None, Args(reporter)) and assert the graph is empty afterwards. That covers the fix's own call site without depending on suite execution order, and fails when ComponentSuite#afterAll is removed.

Cleanup lives in afterAll so a failed assertion cannot leak the cycle the suite registers. I verified each test fails against the corresponding mutant, and that mvn -pl gluten-core,gluten-substrait test passes (37 + 51), both in the natural suite order and with ComponentSuite forced to run first.

Closes #12655

The component graph and the discovery latch are JVM-global. ComponentSuite
registers dummy components into the graph, including a deliberate dependency
cycle, and never removes them, so any later suite in the same JVM that calls
Component#sorted fails with "Cycle detected in the component graph: B, D, C".
No suite in gluten-core happens to call it after ComponentSuite today, which is
why this has gone unnoticed, but eight call sites in main reach it, including
GlutenDriverPlugin#init and GlutenSessionExtensions, so any future suite that
boots a SparkContext or builds session extensions hits it.

Add a testing-only reset that empties the graph, clears each component's
registration flag so it can register again, and unlatches discovery. Call it from
ComponentSuite#afterAll.

Add ComponentGraphResetSuite as the guard: it registers a cycle, asserts sorting
reports it, resets, then asserts sorting succeeds and the dummy components are
gone. Verified it fails when the reset body is emptied.
The guard suite only caught an all-or-nothing regression. Deleting the flag
reset in Registry#clear, deleting the latch re-arm, or deleting ComponentSuite's
afterAll override each left gluten-core fully green, because the suite asserted
through sortedUnsafe() and its final assertion was vacuous once the graph was
empty.

Rewrite the suite around two mutants that now fail:
  - clear then re-register the same instance, which only works if Registry#clear
    resets the registration flag;
  - run ComponentSuite in-process and assert the graph is empty afterwards, which
    covers the fix's own call site without depending on suite execution order.
The cycle test now asserts isEmpty rather than absence-by-name, and cleanup moved
to afterAll so a failed assertion cannot leak the cycle it registers.

Keep the flag and the graph in sync: ensureRegistered rolls the flag back when
graph.add throws, since a component that never entered the graph is invisible to
Registry#clear and could otherwise never register again. The rollback covers only
graph.add; extending it over dependencies() would roll back a component that is
already in the graph, making the next registration fail with a misleading
"UID already registered".

Make resetRegisteredForTesting final. Nine of the eleven in-repo Component
implementations sit in this package and could otherwise override it to a no-op
and silently defeat the clear.

Narrow the clearAllForTesting scaladoc to what it actually guarantees: values
derived from an earlier Component.sorted() are not reset, and rediscovery
installs fresh instances. Note on the latch line that it exists for the backend
modules, whose classpath carries component files, so it does not read as dead
code in gluten-core.
Copilot AI review requested due to automatic review settings July 30, 2026 06:03
@github-actions github-actions Bot added the CORE works for Gluten Core label Jul 30, 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 PR fixes a JVM-global test isolation problem in gluten-core where ComponentSuite registers dummy components (including a deliberate dependency cycle) into the global component graph and leaves them behind, breaking later suites that call Component.sorted().

Changes:

  • Add a testing-only reset (clearAllForTesting) that clears the component graph and re-arms the discovery latch.
  • Make Component.ensureRegistered() roll back its registration flag if graph.add() fails, and ensure graph clearing resets per-component registration flags.
  • Add ComponentGraphResetSuite to validate the reset behavior and that ComponentSuite cleans up in afterAll.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
gluten-core/src/test/scala/org/apache/gluten/component/ComponentSuite.scala Clears the JVM-global component graph in afterAll to avoid leaking dummy components into later suites.
gluten-core/src/test/scala/org/apache/gluten/component/ComponentGraphResetSuite.scala Adds regression tests proving the graph reset works and that ComponentSuite leaves the graph empty.
gluten-core/src/main/scala/org/apache/gluten/component/package.scala Introduces clearAllForTesting() to clear the graph and re-arm component discovery.
gluten-core/src/main/scala/org/apache/gluten/component/Component.scala Adds graph-clearing hooks and registration-flag reset logic to support test isolation and re-registration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Catching Throwable also matches VirtualMachineError and friends, where rolling
back a registration flag serves no purpose. graph.add only throws
IllegalArgumentException from its own require checks, so NonFatal covers every
case the rollback is meant for.
Copilot AI review requested due to automatic review settings July 30, 2026 06:14
@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

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

gluten-core/src/test/scala/org/apache/gluten/component/ComponentGraphResetSuite.scala:94

  • FailureCollectingReporter mutates a shared mutable.Buffer without any synchronization. ScalaTest can deliver Reporter events from different threads (e.g., when suites/tests are run concurrently), which can lead to races/corruption and flaky failures when the nested ComponentSuite is run.
  private class FailureCollectingReporter extends Reporter {
    private val buffer = mutable.Buffer[String]()

    def failures: Seq[String] = buffer.toSeq

gluten-core/src/main/scala/org/apache/gluten/component/package.scala:59

  • Scaladoc grammar is a bit confusing here: "Neither are the per-vertex ... flags" is missing the verb (e.g., "reset"). Rewording makes the behavior and non-reset guarantees clearer.
   * Only the graph and the latch are reset. Values derived from an earlier [[Component.sorted]] are
   * not: `BackendsApiManager.backend`, `GlutenCostModel.costModelRegistry` and the `graphCache`
   * inside `Transition.factory` keep what they computed from the pre-clear component set. Neither
   * are the per-vertex `TransitionGraph.Vertex.initialized` flags, so a re-registered component
   * does not add its transition edges a second time. Rediscovery also constructs fresh component

@LuciferYang

Copy link
Copy Markdown
Contributor Author

cc @jackylee-ch

@jackylee-ch

Copy link
Copy Markdown
Contributor

cc @zhztheplayer

@zhztheplayer
zhztheplayer merged commit 4d0ba0d into apache:main Jul 31, 2026
72 checks passed
@LuciferYang

Copy link
Copy Markdown
Contributor Author

Thank you @zhztheplayer @jackylee-ch

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.

ComponentSuite leaks its dummy components into the JVM-global component graph

4 participants