Skip to content

[SPARK-54055][CORE] Clean up Python worker factories on session eviction - #59713

Open
psavalle wants to merge 3 commits into
apache:masterfrom
psavalle:pa/fix-udf-daemon-leaks
Open

psavalle wants to merge 3 commits into
apache:masterfrom
psavalle:pa/fix-udf-daemon-leaks

Conversation

@psavalle

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

The PR ties Python worker factories to the session lifecycle:

  • Adds the session artifact UUID and driver/executor ownership to each factory’s cache key.
  • On executor session eviction, stops and removes matching task-worker factories before deleting session files.
  • Cleans driver-side planner factories when the session’s ArtifactManager closes.
  • Ensures workers finishing after their factory was removed are closed instead of returned to a stale pool.
  • Stops daemon monitor threads and worker processes when a factory is shut down.

This prevents reuse of daemons whose working directory was deleted and avoids per-session process/thread leaks.

Why are the changes needed?

This addresses:

  • SPARK-54055: Spark Connect sessions leak pyspark UDF daemon processes and threads

  • SPARK-50118: Spark removes working directory while Python UDF runs. There was an attempt in the past to fix this, but it was eventually reverted.

Spark caches a separate PythonWorkerFactory for each combination of Python configuration and session artifact UUID. Each factory may own a long-live Python daemon, reusable Python workers, an idle-worker monitor thread.

For session-isolated workloads, those processes are launched with the session’s artifact directory as their working directory, for example:

<SparkFiles root>/<session UUID>/

We can hit the following scenario:

  1. An executor runs Python tasks for an isolated Spark session.
  2. Spark creates and caches a PythonWorkerFactory, its daemon starts inside the session directory.
  3. The executor’s isolated-session cache later evicts that session.
  4. IsolatedSessionState.cleanup() deletes the session directory and classloader resources.
  5. The corresponding Python worker factory is not removed or stopped. The daemon therefore remains alive with its current working directory pointing to a directory that has been deleted.

If the same session is used again, Spark may reuse that factory and daemon ,which can lead to issues.

Even if the session is never used again, every evicted session can leave behind resources (daemon/worker processes, monitor thread) forever.

Does this PR introduce any user-facing change?

This fixes bugs/leaks.

How was this patch tested?

Unit and integration tests.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: codex-cli 0.161.0

@psavalle

Copy link
Copy Markdown
Contributor Author

@HyukjinKwon would you be able to take a look? This should fix https://issues.apache.org/jira/browse/SPARK-50118 at which you had a stab a while back but had to be reverted. Thank you!

@HyukjinKwon HyukjinKwon added the ai-review-requested Trigger AI review by Omnigent on Databricks label Oct 22, 2026

@spark-test spark-test 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.

Review summary

The executor-side fix is sound. Tying factories to IsolatedSessionState.cleanup() behind the existing eviction refcount stops a session's daemon, pooled workers and monitor thread before its directory is deleted, and a later task for the same UUID gets a fresh factory. PythonWorkerFactory.stop() now also ends the idle-worker monitor thread.

Two defects remain. The new removal path makes a late destroyPythonWorker/releasePythonWorker (reachable from PythonRunner.MonitorThread) silently drop its worker instead of closing it, which contradicts the description's guarantee for late workers. The new UDTF eviction test's cleanup also cannot reach the driver-owned planner factory that the test itself creates. A question for the author about driver-side planner cleanup follows.

Cross-cutting observations

  • The leaked daemon in the new UDTF eviction test and the uncleaned driver planner factories in Spark Connect sessions have the same cause. Driver-owned factories created by PythonPlannerRunner can currently only be removed by SparkEnv.stop(), because the new session cleanup deliberately skips them and ArtifactManager has no hook for them.
    Separation: The test cleanup can be fixed on its own with a narrow removal helper. Whether production session close should also use such a path is the author's decision under the driver-planner question. This pattern does not merge their repairs or settle that decision.

Findings

2 total: 0 P0, 0 P1, 1 P2, 1 P3.

Non-blocking (P2)

  • Late destroy/release after factory removal silently drops the worker — core/src/main/scala/org/apache/spark/SparkEnv.scala:404 — see inline.

Nit (P3)

  • UDTF eviction test cleanup cannot remove the driver-owned planner factory it creates — sql/core/src/test/scala/org/apache/spark/sql/execution/python/PythonUDTFSuite.scala:206 — see inline.

Decision challenges

Driver-owned planner factories are never cleaned on session close, despite the PR description

The PR description says driver-side planner factories are cleaned when the session's ArtifactManager closes. In the diff, removePythonWorkerFactories explicitly skips driverOwned keys, every PythonPlannerRunner factory is now driverOwned = true, and ArtifactManager is unchanged with no reference to Python worker factories. Apart from SparkEnv.stop(), nothing removes these factories. So on a Spark Connect driver, every session that runs a UDTF analyze() or a Python data source planner with the daemon keeps its planner daemon and pooled workers after the session closes. In local mode that daemon also keeps running in the session directory that executor cleanup deletes, and a later planner call for the same session reuses it. This is the shape the PR fixes for executor factories. Did you intend to add ArtifactManager-driven cleanup of driver-owned factories in this PR? Or should the description drop that claim and record driver planner cleanup as a follow-up?

Generated by Omnigent on Databricks

Comment thread core/src/main/scala/org/apache/spark/SparkEnv.scala Outdated
@spark-test spark-test removed the ai-review-requested Trigger AI review by Omnigent on Databricks label Oct 22, 2026
@psavalle

Copy link
Copy Markdown
Contributor Author

Thanks, I've addressed the comments

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants