[
https://issues.apache.org/jira/browse/SPARK-59432?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
ASF GitHub Bot updated SPARK-59432:
-----------------------------------
Labels: pull-request-available (was: )
> Support free-threaded (no-GIL) CPython in pyspark-client-rust
> -------------------------------------------------------------
>
> Key: SPARK-59432
> URL: https://issues.apache.org/jira/browse/SPARK-59432
> Project: Spark
> Issue Type: New Feature
> Components: Connect
> Affects Versions: connect-rust-4.2.0
> Reporter: James Willis
> Priority: Minor
> Labels: pull-request-available
>
> h2. Background
> {{pyspark-client-rust}} is a PyO3 extension. On free-threaded CPython it does
> not currently work GIL-off, for two independent reasons:
> # The {{#[pymodule]}} in {{crates/pyspark-rs/src/lib.rs}} does not declare
> {{{}gil_used = false{}}}. CPython re-enables the GIL process-wide when it
> imports an extension module that has not opted in, so even on a free-threaded
> interpreter the GIL is silently switched back on.
> # The published wheels are {{{}cp39-abi3{}}}. The stable ABI (abi3) and the
> free-threaded ABI are mutually exclusive, so no wheel is selectable on a
> free-threaded interpreter at all.
> h2. Why this matters
> A pure-Rust (tonic) Spark Connect client is uniquely able to run with the GIL
> disabled: unlike the classic PySpark client, it pulls in no {{{}grpcio{}}},
> which otherwise re-enables the GIL on import (grpc/grpc#38762).
> Free-threading lets a Python application drive Spark Connect with real thread
> parallelism on the client side.
> h2. Proposed change
> # Declare {{#[pymodule(gil_used = false)]}} on {{_pyspark
> }}({{{}crates/pyspark-rs/src/lib.rs{}}}). This is inert on GIL-enabled
> builds, so it is safe to ship on its own.
> # Add a free-threaded wheel build leg (cp313t / cp314t) to
> {{{}release.yml{}}}, in addition to the existing abi3 leg.
> # Add a smoke test that imports {{pyspark_client_rust}} on a free-threaded
> interpreter and asserts {{{}sys._is_gil_enabled() is False{}}}.
> h2. Safety
> The crate is already thread-safe, so {{gil_used = false}} is a truthful
> assertion:
> * The global tokio runtime is a {{OnceLock<Runtime>}} (multi-threaded), and
> every blocking call is wrapped in {{py.detach(|| block_on(...))}} (GIL
> released while blocking).
> * All process-global state is behind {{OnceLock}} / {{Mutex}} / atomics
> (e.g. the active-session {{{}OnceLock<Mutex<Option<SparkSession>>>{}}}).
> * Every {{#[pyclass]}} is {{Sync}} (PyO3's free-threaded requirement,
> enforced at compile time): the core {{SparkSession}} is built from {{Arc}} /
> {{Arc<Mutex<..>> }}/ atomics; stored {{Py<T>}} handles are {{{}Send +
> Sync{}}}; the response stream's {{tonic::Streaming}} is made {{Sync}} via
> {{{}sync_wrapper::SyncWrapper{}}}.
> * No {{Rc}} / {{RefCell}} / {{Cell}} / {{static mut}} anywhere in the
> extension.
> Behavioral note (not a soundness issue): under free-threading, two Python
> threads invoking a {{&mut self}} method on the *same* object instance get a
> {{RuntimeError: Already borrowed}} rather than corrupting state. That surface
> is small (consume-once builders, {{{}ResponseStream::_{_}next{_}_{}}}) and
> this is the documented free-threaded contract.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]