Skip to content

Work around Dataproc Py4J weak container references [reduced-it] [fast-ut] [databricks] - #15880

Open
wjxiz1992 wants to merge 2 commits into
NVIDIA:mainfrom
wjxiz1992:codex/15805-dataproc-py4j-workaround
Open

Work around Dataproc Py4J weak container references [reduced-it] [fast-ut] [databricks]#15880
wjxiz1992 wants to merge 2 commits into
NVIDIA:mainfrom
wjxiz1992:codex/15805-dataproc-py4j-workaround

Conversation

@wjxiz1992

@wjxiz1992 wjxiz1992 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

JaCoCo production line coverage: not fully measurable locally — current nightly baseline artifacts were not applied; all JVM production modules are N/A for this Python-only integration-test harness change.

Refs #15805.

Description

Dataproc 2.2.86 changes Py4J's JavaMember.container from an upstream strong reference to a weak reference. A temporary JavaObject can therefore be finalized, and its JVM target deleted, before a chained call such as scala_map.get(key).get() reaches JavaMember.__call__.

This process-local workaround follows the runtime patch recommended by the Google Cloud Dataproc support team in Customer Care case 74970097 (P2): restore the upstream strong-reference behavior while their internal investigation continues. The support case link is intentionally omitted; please contact me offline if the exact link is needed.

The change applies only to the cudf-spark integration-test process before SparkSession creation; it is not a Dataproc/Py4J platform fix and does not change the cudf-spark product JAR. It probes the installed JavaMember behavior with an inert container and installs the wrapper only when the container is actually stored as a weak reference. The wrapper is idempotent, and probe or initialization failures are logged without aborting the pytest worker. Standard upstream Py4J remains unmodified.

Scope: this covers the Py4J missing-target failure family described in #15805. It does not address the separate ORC boolean-encoding signal reported on the same issue.

The regression tests cover the actual scala_map.get(key).get() call shape used by the test harness, strong-reference retention after garbage collection, idempotent installation, the upstream no-op path, and fail-open behavior when the probe cannot run. This is a driver-side Py4J lifetime path rather than a Spark query: CPU and GPU session helpers share the same SparkSession and gateway and only toggle RAPIDS SQL configuration, so the focused test intentionally exercises the failing chained call directly instead of introducing an unrelated query or execution-plan assertion.

Review focus: whether behavior-based detection is the right temporary boundary while Dataproc owns the permanent platform fix.

Validation completed locally:

  • python3 -m py_compile for both changed Python files.
  • Standalone helper regression tests: 4 passed, 1 deselected in 0.04s.
  • Spark 3.5.3, Python 3.12.3, Py4J 0.10.9.7, four xdist workers, upstream Py4J: 5 passed in 25.74s.
  • The same Spark/Python/xdist run with the Dataproc weak-container behavior injected into a temporary Py4J copy: 5 passed in 22.97s.
  • git diff --check.

AI assistance: The change and PR description were prepared with Codex assistance.

Checklists

Documentation

  • Updated for new or modified user-facing features or behaviors
  • No user-facing change

Testing

  • Added or modified tests to cover new code paths
  • Covered by existing tests
    (Please provide the names of the existing tests in the PR description.)
  • Not required

Performance

  • Tests ran and results are added in the PR description
  • Issue filed with a link in the PR description
  • Not required

Signed-off-by: Allen Xu <allxu@nvidia.com>
@wjxiz1992 wjxiz1992 added the test Only impacts tests label Sep 3, 2026
@wjxiz1992 wjxiz1992 self-assigned this Sep 3, 2026
@wjxiz1992 wjxiz1992 changed the title [WIP] Work around Dataproc Py4J weak container references [reduced-it] [fast-ut] [WIP] Work around Dataproc Py4J weak container references [reduced-it] [databricks] [fast-ut] Sep 3, 2026

@firestarman firestarman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline notes on the probe, session-start hook, and regression coverage. The overall approach still looks right for a temporary IT-only workaround.

container = _ProbeContainer()
member = java_member_class(
'_spark_rapids_container_lifetime_probe', container, 'o0', _ProbeGatewayClient())
return isinstance(member.container, weakref.ReferenceType)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probe constructs JavaMember with a fake gateway client and is invoked from pytest_sessionstart with no try/except. If Dataproc's patched __init__ touches more of the gateway than upstream, the xdist worker fails before any test runs. Probe failures should log and skip the patch rather than abort session startup.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated.

import pyspark
from py4j.java_gateway import java_import

# Dataproc 2.2.86 can delete temporary JVM targets before chained Py4J calls complete.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a process-wide monkeypatch of the IT harness, not a Dataproc/Py4J platform fix. That boundary is fine, but a green Linux/Databricks IT run should not be read as fixing user pyspark jobs on 2.2.86. Also worth wrapping this call so a probe exception cannot take down the whole worker.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

logging.warning(
"Detected weak Py4J JavaMember container references; applying the temporary "
"strong-reference workaround for https://github.com/NVIDIA/cudf-spark/issues/15805")
return True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The weak-container analysis explained the Jenkins #249 missing-target failures. The original #248 ORC boolean-encoding signal on #15805 was called out as separate, so this workaround should not be treated as covering every failure on that issue.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

def test_py4j_chained_temporary_container_call():
spark = get_spark_i_know_what_i_am_doing()

assert spark._jvm.java.util.Collections.emptyList().size() == 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dataproc failures were chained lookups such as scala_map.get(key).get() in spark_session.py. emptyList().size() is always green on upstream Py4J, and the other tests use in-process fake classes, so they validate the helper rather than Dataproc JavaMember. Linux/Databricks premerge will not catch a regression of the real call path. A test closer to Map.get().get(), plus evidence from a 2.2.86 image or an equivalent injected-Py4J IT run, would make this much stronger.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Signed-off-by: Allen Xu <allxu@nvidia.com>
@wjxiz1992 wjxiz1992 changed the title [WIP] Work around Dataproc Py4J weak container references [reduced-it] [databricks] [fast-ut] [WIP] Work around Dataproc Py4J weak container references [reduced-it] [fast-ut] [databricks] Sep 3, 2026
@wjxiz1992 wjxiz1992 changed the title [WIP] Work around Dataproc Py4J weak container references [reduced-it] [fast-ut] [databricks] [WIP] Work around Dataproc Py4J weak container references [reduced-it] [databricks] [fast-ut] Sep 3, 2026
@wjxiz1992
wjxiz1992 marked this pull request as ready for review September 4, 2026 06:24
Copilot AI lite review requested due to automatic review settings September 4, 2026 06:24
@wjxiz1992 wjxiz1992 changed the title [WIP] Work around Dataproc Py4J weak container references [reduced-it] [databricks] [fast-ut] Work around Dataproc Py4J weak container references [reduced-it] [databricks] [fast-ut] Sep 4, 2026
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a process-local integration-test workaround that restores strong Py4J JavaMember container references when a runtime probe detects Dataproc’s weak-reference behavior.

  • Installs the conditional, idempotent wrapper before creating each pytest worker’s SparkSession.
  • Logs and continues when probing or initialization fails.
  • Adds focused tests for chained Scala-map access, retention across garbage collection, idempotency, upstream no-op behavior, and probe failure.

Confidence Score: 4/5

The PR appears safe to merge, with a non-blocking integration-test coverage issue around exercising the chained call through both CPU and GPU session configurations.

The workaround is narrowly installed before the shared Spark session and is guarded by behavior detection and fail-open handling; the remaining concern is that its Spark-backed regression test uses only the default shared session path.

Files Needing Attention: integration_tests/src/main/python/py4j_workaround_test.py

Important Files Changed

Filename Overview
integration_tests/src/main/python/spark_init_internal.py Adds behavior-based Py4J weak-container detection and a fail-open, process-local strong-reference wrapper before SparkSession startup.
integration_tests/src/main/python/py4j_workaround_test.py Adds focused workaround regression coverage, but the Spark-backed case bypasses the required CPU/GPU integration assertion path.

Reviews (1): Last reviewed commit: "Address Py4J workaround review feedback" | Re-trigger Greptile

Comment on lines +28 to +35

def test_py4j_chained_scala_map_get():
spark = get_spark_i_know_what_i_am_doing()
scala_map = spark.conf._jconf.getAll()
keys = scala_map.keys().iterator()

assert keys.hasNext()
key = keys.next()

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.

P2 Missing CPU/GPU session coverage

This Spark-backed regression invokes the chained JVM map access only through the shared default session, so it can pass without confirming that the same call remains functional across the harness's CPU and GPU session configurations. Use the repository's CPU/GPU comparison or fallback assertion path for this integration test.

Rule Used: Integration tests must verify GPU execution using ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regression targets driver-side Py4J JavaMember lifetime, not Spark query execution. with_cpu_session and with_gpu_session share the same SparkSession and gateway and only toggle RAPIDS SQL configuration, so they do not exercise a different lifetime path. The GPU/fallback assertion helpers require a DataFrame and execution plan, while this test intentionally exercises the exact scala_map.get(key).get() failure shape directly after the workaround is installed before SparkSession creation. Importing spark_session to use those helpers also runs the vulnerable _from_scala_map path during module initialization. A CPU/GPU split is therefore not applicable here.

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.

You're right — this regression is specifically about driver-side Py4J object lifetime, and the CPU/GPU helpers would not provide additional coverage because they reuse the same SparkSession and gateway while only changing RAPIDS SQL configuration. They also introduce the unrelated DataFrame/plan path and can trigger the vulnerable initialization earlier. The direct chained-call test is the appropriate coverage here, so the CPU/GPU session coverage request does not apply.

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.

🔵 Needs a closer look

It introduces a runtime monkey-patch of Py4J internals that could have subtle cross-test effects and warrants final human design review despite the added regression tests.

Pull request overview

This PR adds a process-local integration-test workaround for a Dataproc 2.2.86 Py4J behavior change where JavaMember.container becomes a weak reference, which can allow temporary JavaObject targets to be finalized before chained calls complete (e.g., scala_map.get(key).get()). The workaround probes the installed Py4J behavior and, only when needed, monkey-patches JavaMember.__init__ to restore a strong container reference for the test process prior to SparkSession creation.

Changes:

  • Add a behavior-based probe and conditional monkey-patch to restore strong JavaMember.container semantics in the integration-test process.
  • Invoke the workaround during pytest session startup before creating the SparkSession.
  • Add regression tests covering the chained-call shape, strong-reference retention, idempotency, upstream no-op behavior, and fail-open probe behavior.
File summaries
File Description
integration_tests/src/main/python/spark_init_internal.py Adds conditional Py4J JavaMember container-lifetime workaround and applies it early in pytest session startup.
integration_tests/src/main/python/py4j_workaround_test.py Adds regression tests validating detection/patching behavior and exercising the chained-call pattern that previously failed.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@wjxiz1992

Copy link
Copy Markdown
Collaborator Author

build

@wjxiz1992 wjxiz1992 changed the title Work around Dataproc Py4J weak container references [reduced-it] [databricks] [fast-ut] Work around Dataproc Py4J weak container references [reduced-it] [fast-ut] [databricks] Sep 4, 2026

@gerashegalov gerashegalov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wjxiz1992

Copy link
Copy Markdown
Collaborator Author

build

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test Only impacts tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants