diff --git a/docs/porting.md b/docs/porting.md index e636e11..13bd3cf 100644 --- a/docs/porting.md +++ b/docs/porting.md @@ -15,7 +15,7 @@ free-threading, many more users will want to use Python threads. This means we must analyze Python codebases, particularly in low-level extension modules, to identify thread safety issues and make changes to thread-unsafe -low-level code, including C, C++, and Cython code exposed to Python. +low-level code, including C, C++, Cython, and Rust code exposed to Python. ## Updating Extension Modules @@ -79,8 +79,25 @@ after importing a module that does not support the GIL. ``` === "Cython" - Starting with Cython 3.1.0 (only available via the nightly wheels or the `master` - branch as of right now), extension modules written in Cython can do so using the + Cython code can be thread-unsafe and just like C and C++ code can exhibit + undefined behavior due to data races. + + Code operating on Python objects should not exhibit any low-level data corruption + or C undefined behavior due to Python-level semantics. If you find such a + case, it may be a Cython or CPython bug and should be reported as such. + + That said, as opposed to data races, race conditions that produces random + results from a multithreaded algorithm are not undefined behavior and are + allowed in Python and therefore Cython as well. You will still need to add + locking or synchronization where appropriate to ensure reproducible results + when running a multithreaded algorithm on shared mutable data. See the + [suggested plan of attack](porting.md#suggested-plan-of-attack) below for + more details about discovering and fixing thread safety issues for Python + native extensions. + + Starting with Cython 3.1.0 (available via the nightly wheels, a PyPI + pre-release or the `master` branch as of right now), extension modules + written in Cython can do so using the [`freethreading_compatible`](https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#compiler-directives) compiler directive. @@ -156,9 +173,49 @@ after importing a module that does not support the GIL. free-threaded builds. See [the docs on setting up CI](ci.md) for advice on how to build projects that depend on Cython. +=== "Rust" + If you use the CPython C API via [PyO3](https://pyo3.rs), then you + can follow the [PyO3 Guide + section](https://pyo3.rs/latest/free-threading.html) on supporting + free-threaded Python. You must also update your extension to at least + version 0.23. + + You should write multithreaded tests of any code you expose to Python. See + the details about testing in our [suggested plan of + attack](porting.md#suggested-plan-of-attack) below as well as the guidance + for [updating test suites](porting.md#fixing-thread-unsafe-tests). You + should fix any thread safety issues you discover while running multithreaded + tests. + + As of PyO3 0.23, PyO3 enforces Rust's borrow checking rules at + runtime and may produce runtime panics if you simultaneously mutably borrow + data in more than one thread. You may want to consider storing state in using + atomic data structures, with mutexes or locks, or behind `Arc` + pointers. + + Once you are satisfied the Python modules defined by your rust crate are + thread safe, you can pass `gil_used = false` to the [`pymodule` + macro](https://docs.rs/pyo3/latest/pyo3/attr.pymodule.html): + + ```rust + + #[pymodule(gil_used = false)] + fn my_module(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { + ... + } + ``` + + If you define any modules procedurally by manually creating a `PyModule` + struct without using the `pymodule` macro, you can call + [`PyModuleMethods::gil_used`](https://docs.rs/pyo3/latest/pyo3/prelude/trait.PyModuleMethods.html#tymethod.gil_used) + after instantiating the module. + + If you use the `pyo3-ffi` crate and/or `unsafe` FFI calls to call directly into the C + API, then see the section on porting C extensions in this guide as well as + the PyO3 source code. + === "f2py" - Starting with NumPy 2.1.0 (only available via the nightly wheels or the - `main` branch as of right now), extension modules containing f2py-wrapped + Starting with NumPy 2.1.0, extension modules containing f2py-wrapped Fortran code can declare they are thread-safe and support free-threading using the [`--freethreading-compatible`](https://numpy.org/devdocs/f2py/usage.html#extension-module-construction) @@ -176,6 +233,8 @@ to also add support for the free-threaded build. ## Suggested Plan of Attack +### Validating thread safety with testing + Put priority on thread safety issues surfaced by real-world testing. Run the test suite for your project and fix any failures that occur only with the GIL disabled. Some issues may be due to changes in Python 3.13 that are not @@ -185,14 +244,20 @@ Definitely run your existing test suite with the GIL disabled, but unless your tests make heavy use of the `threading` module, you will likely not hit many issues, so also consider constructing multithreaded tests to expose bugs based on workflows you want to support. Issues found in these tests are the issues -your users will most likely hit first. The -[`concurrent.futures.ThreadPoolExecutor`](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor) -class is a lightweight way to create multithreaded tests where many threads -repeatedly call a function simultaneously. You can also use the `threading` -module directly. Adding a `threading.Barrier` before your test code is a good -way to synchronize workers and encourage a race condition. - -You can also look at +your users will most likely hit first. + +Multithreaded Python programs can exhibit [race +conditions](https://en.wikipedia.org/wiki/Race_condition) which produce random +results depending on the order of execution in a multithreaded context. This can +happen even with the GIL providing locking, so long as the algorithm releases +the GIL at some point, and many Python operations can lead to the GIL being +released at some point. If your library was not designed with multithreading in +mind, it is likely that some form of locking or synchronization is necessary to +make mutable data structures defined by your library thread-safe. You should +document the thread-safety guarantees of your library, both with and without the +GIL. + +You can look at [pytest-run-parallel](https://github.com/Quansight-Labs/pytest-run-parallel) as well as [pytest-freethreaded](https://github.com/tonybaloney/pytest-freethreaded), which @@ -204,19 +269,25 @@ below on [global state in tests](porting.md#dealing-with-global-state-in-tests) for more information about updating test suites to work with the free-threaded build. -Many C and C++ extensions assume the GIL serializes access to state shared +These plugins are useful for discovering issues related to use of global state, +but cannot discover issues from multithreaded use of data structures defined by +your library. + +If you would like to create your own testing utilities, the +[`concurrent.futures.ThreadPoolExecutor`](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor) +class is a lightweight way to create multithreaded tests where many threads +repeatedly call a function simultaneously. You can also use the `threading` +module directly for more complicated multithreaded test workflows. Adding a +`threading.Barrier` before a line of code that you suspect will trigger a race +condition is a good way to synchronize workers and increase the chances that an +infrequent test failure will trigger. + +### General considerations for porting + +Many extensions assume the GIL serializes access to state shared between threads, introducing the possibility of data races and race conditions that are impossible when the GIL is enabled. -Cython code can also be thread-unsafe and exhibit undefined behavior due to -data races just like any other C or C++ code. However, code operating on Python -objects should not exhibit any low-level data races or undefined behavior due -to Python-level semantics. If you find such a case, it may be a Cython or -CPython bug and should be reported as such. That said, race conditions are -allowed in Python and therefore Cython as well, so you will still need to add -locking or synchronization where appropriate to ensure reproducible results -when running a multithreaded algorithm on shared mutable data. - The CPython C API exposes the `Py_GIL_DISABLED` macro in the free-threaded build. You can use it to enable low-level code that only runs under the free-threaded build, isolating possibly performance-impacting changes to the @@ -250,7 +321,7 @@ multithreaded scaling. For your libraries, we suggest a similar approach for now. Focus on thread safety issues that only occur with the GIL disabled. Any non-critical -pre-existing thread safety issues can be dealt with later once the +preexisting thread safety issues can be dealt with later once the free-threaded build is used more. The goal for now should be to enable further refinement and experimentation by fixing issues that prevent using the library at all. diff --git a/docs/tracking.md b/docs/tracking.md index 3913ea8..f774142 100644 --- a/docs/tracking.md +++ b/docs/tracking.md @@ -31,40 +31,46 @@ please open an issue on [this issue tracker](https://github.com/Quansight-Labs/f -| Project | Upstream issue | Tested in CI | PyPI release | First version with support | Nightly wheels | Nightly link | -| :------------------- | :--------------------------------------------------------------------------: | :-------------------: | :-------------------: | :------------------------: | :--------------------------------------------------------: | :--------------------------------------------------------------------------------------: | -| Bazel (rules-python) | [:simple-github:](https://github.com/bazelbuild/rules_python/issues/2386) | | n/a | 0.39.0 | | | -| cffi | [:simple-github:](https://github.com/python-cffi/cffi/issues/126) | :material-check-bold: | | 1.18 | | | -| cibuildwheel | [:simple-github:](https://github.com/pypa/cibuildwheel/issues/1657) | :material-check-bold: | :material-check-bold: | 2.19 | | | -| CMake | | | :material-check-bold: | 3.30.0 | | | -| ContourPy | [:simple-github:](https://github.com/contourpy/contourpy/issues/407) | :material-check-bold: | :material-check-bold: | 1.3.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/contourpy/) | -| Cython | [:simple-github:](https://github.com/cython/cython/issues/6221) | :material-check-bold: | | 3.1.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/cython/) | -| JAX | [:simple-github:](https://github.com/jax-ml/jax/issues/23073) | :material-check-bold: | :material-check-bold: | 1.4.2 | | | -| joblib | [:simple-github:](https://github.com/joblib/joblib/issues/1592) | :material-check-bold: | :material-check-bold: | 1.4.2 | | | -| jupyterlab | [:simple-github:](https://github.com/jupyterlab/jupyterlab/issues/16915) | | | | | | -| matplotlib | [:simple-github:](https://github.com/matplotlib/matplotlib/issues/28611) | :material-check-bold: | :material-check-bold: | 3.9.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/matplotlib/) | -| Meson | [:simple-github:](https://github.com/mesonbuild/meson/issues/13263) | | :material-check-bold: | 1.5.0 | | | -| meson-python | [:simple-github:](https://github.com/mesonbuild/meson-python/issues/499) | :material-check-bold: | :material-check-bold: | 0.16.0 | | | -| ml-dtypes | [:simple-github:](https://github.com/jax-ml/ml_dtypes/pull/168) | :material-check-bold: | | | | | -| mlir-python | [:simple-github:](https://github.com/llvm/llvm-project/issues/105522) | | | | | | -| multidict | [:simple-github:](https://github.com/aio-libs/multidict/issues/1014) | :material-check-bold: | | | | | -| mypyc | [:simple-github:](https://github.com/mypyc/mypyc/issues/1038) | | | | | | -| Nuitka | [:simple-github:](https://github.com/Nuitka/Nuitka/issues/3062) | | | | | | -| NumPy | [:simple-github:](https://github.com/numpy/numpy/issues/26157) | :material-check-bold: | :material-check-bold: | 2.1.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/numpy/) | -| nanobind | [:simple-github:](https://github.com/wjakob/nanobind/pull/695) | :material-check-bold: | :material-check-bold: | 2.2.0 | | | -| packaging | [:simple-github:](https://github.com/pypa/packaging/issues/727) | :material-check-bold: | :material-check-bold: | 24.0 | | | -| pandas | [:simple-github:](https://github.com/pandas-dev/pandas/issues/59057) | :material-check-bold: | :material-check-bold: | 2.2.3 | :simple-linux: :simple-apple: :material-microsoft-windows: | | -| Pillow | [:simple-github:](https://github.com/python-pillow/Pillow/issues/8199) | :material-check-bold: | :material-check-bold: | 11.0.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/pillow/) | -| pip | [:simple-github:](https://github.com/pypa/pip/issues/12634) | :material-check-bold: | :material-check-bold: | 24.1 | | | -| PyArrow | [:simple-github:](https://github.com/apache/arrow/issues/43536) | :material-check-bold: | | 18.0.0 | :simple-linux: :simple-apple: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/pyarrow/) | -| pybind11 | [:simple-github:](https://github.com/pybind/pybind11/issues/5112) | :material-check-bold: | :material-check-bold: | 2.13 | | | -| PyO3 | [:simple-github:](https://github.com/PyO3/pyo3/issues/4265) | :material-check-bold: | | 0.23 | | | -| PyTorch | [:simple-github:](https://github.com/pytorch/pytorch/issues/130249) | :material-check-bold: | | 2.6.0 | :simple-linux: | [:simple-pytorch:](https://pytorch.org/get-started/locally/#start-locally) | -| PyWavelets | | :material-check-bold: | :material-check-bold: | 1.7.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/pywavelets/) | -| scikit-build-core | | :material-check-bold: | :material-check-bold: | 0.9.5 | | | -| scikit-image | [:simple-github:](https://github.com/scikit-image/scikit-image/issues/7464) | :material-check-bold: | | 0.25.0 | :simple-linux: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/scikit-image/) | -| scikit-learn | [:simple-github:](https://github.com/scikit-learn/scikit-learn/issues/28978) | :material-check-bold: | | 1.6.0 | :simple-linux: :simple-apple: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/scikit-learn/) | -| SciPy | [:simple-github:](https://github.com/scipy/scipy/issues/20669) | :material-check-bold: | | 1.15.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/scipy/) | -| setuptools | | :material-check-bold: | :material-check-bold: | 69.5.0 | | | -| wrapt | | :material-check-bold: | :material-check-bold: | 1.17.0.dev3 | | | -| zstandard | [:simple-github:](https://github.com/indygreg/python-zstandard/issues/231) | | | | | | +| Project | Upstream issue | Tested in CI | PyPI release | First version with support | Nightly wheels | Nightly link | +| :------------------- | :--------------------------------------------------------------------------: | :-------------------: | :------------------------: | :------------------------: | :--------------------------------------------------------: | :--------------------------------------------------------------------------------------: | +| Bazel (rules-python) | [:simple-github:](https://github.com/bazelbuild/rules_python/issues/2386) | | n/a | 0.39.0 | | | +| cffi | [:simple-github:](https://github.com/python-cffi/cffi/issues/126) | :material-check-bold: | | 1.18 | | | +| cibuildwheel | [:simple-github:](https://github.com/pypa/cibuildwheel/issues/1657) | :material-check-bold: | :material-check-bold: | 2.19 | | | +| CMake | | | :material-check-bold: | 3.30.0 | | | +| ContourPy | [:simple-github:](https://github.com/contourpy/contourpy/issues/407) | :material-check-bold: | :material-check-bold: | 1.3.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/contourpy/) | +| cryptography | [:simple-github:](https://github.com/pyca/cryptography/issues/11702) | | | | | | +| Cython | [:simple-github:](https://github.com/cython/cython/issues/6221) | :material-check-bold: | | 3.1.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/cython/) | +| hatch | [:simple-github:](https://github.com/pypa/hatch/issues/1801) | | | | | | +| JAX | [:simple-github:](https://github.com/jax-ml/jax/issues/23073) | :material-check-bold: | :material-check-bold: | 1.4.2 | | | +| joblib | [:simple-github:](https://github.com/joblib/joblib/issues/1592) | :material-check-bold: | :material-check-bold: | 1.4.2 | | | +| jupyterlab | [:simple-github:](https://github.com/jupyterlab/jupyterlab/issues/16915) | | | | | | +| matplotlib | [:simple-github:](https://github.com/matplotlib/matplotlib/issues/28611) | :material-check-bold: | :material-check-bold: | 3.9.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/matplotlib/) | +| Meson | [:simple-github:](https://github.com/mesonbuild/meson/issues/13263) | | :material-check-bold: | 1.5.0 | | | +| meson-python | [:simple-github:](https://github.com/mesonbuild/meson-python/issues/499) | :material-check-bold: | :material-check-bold: | 0.16.0 | | | +| ml-dtypes | [:simple-github:](https://github.com/jax-ml/ml_dtypes/pull/168) | :material-check-bold: | | | | | +| mlir-python | [:simple-github:](https://github.com/llvm/llvm-project/issues/105522) | | | | | | +| multidict | [:simple-github:](https://github.com/aio-libs/multidict/issues/1014) | :material-check-bold: | | | | | +| mypyc | [:simple-github:](https://github.com/mypyc/mypyc/issues/1038) | | | | | | +| Nuitka | [:simple-github:](https://github.com/Nuitka/Nuitka/issues/3062) | | | | | | +| NumPy | [:simple-github:](https://github.com/numpy/numpy/issues/26157) | :material-check-bold: | :material-check-bold: | 2.1.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/numpy/) | +| nanobind | [:simple-github:](https://github.com/wjakob/nanobind/pull/695) | :material-check-bold: | :material-check-bold: | 2.2.0 | | | +| orjson | [:simple-github:](https://github.com/ijl/orjson/issues/530) | | | | | | +| packaging | [:simple-github:](https://github.com/pypa/packaging/issues/727) | :material-check-bold: | :material-check-bold: | 24.0 | | | +| pandas | [:simple-github:](https://github.com/pandas-dev/pandas/issues/59057) | :material-check-bold: | :material-check-bold: | 2.2.3 | :simple-linux: :simple-apple: :material-microsoft-windows: | | +| Pillow | [:simple-github:](https://github.com/python-pillow/Pillow/issues/8199) | :material-check-bold: | :material-check-bold: | 11.0.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/pillow/) | +| pip | [:simple-github:](https://github.com/pypa/pip/issues/12634) | :material-check-bold: | :material-check-bold: | 24.1 | | | +| PyArrow | [:simple-github:](https://github.com/apache/arrow/issues/43536) | :material-check-bold: | | 18.0.0 | :simple-linux: :simple-apple: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/pyarrow/) | +| pybind11 | [:simple-github:](https://github.com/pybind/pybind11/issues/5112) | :material-check-bold: | :material-check-bold: | 2.13 | | | +| PyO3 | [:simple-github:](https://github.com/PyO3/pyo3/issues/4265) | :material-check-bold: | :material-check-bold: [^1] | 0.23 | | | +| PyTorch | [:simple-github:](https://github.com/pytorch/pytorch/issues/130249) | :material-check-bold: | | 2.6.0 | :simple-linux: | [:simple-pytorch:](https://pytorch.org/get-started/locally/#start-locally) | +| PyWavelets | | :material-check-bold: | :material-check-bold: | 1.7.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/pywavelets/) | +| rpds-py | [:simple-github:](https://github.com/crate-py/rpds/issues/101) | :material-check-bold: | | | | | +| scikit-build-core | | :material-check-bold: | :material-check-bold: | 0.9.5 | | | +| scikit-image | [:simple-github:](https://github.com/scikit-image/scikit-image/issues/7464) | :material-check-bold: | | 0.25.0 | :simple-linux: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/scikit-image/) | +| scikit-learn | [:simple-github:](https://github.com/scikit-learn/scikit-learn/issues/28978) | :material-check-bold: | | 1.6.0 | :simple-linux: :simple-apple: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/scikit-learn/) | +| SciPy | [:simple-github:](https://github.com/scipy/scipy/issues/20669) | :material-check-bold: | | 1.15.0 | :simple-linux: :simple-apple: :material-microsoft-windows: | [:simple-anaconda:](https://anaconda.org/scientific-python-nightly-wheels/scipy/) | +| setuptools | | :material-check-bold: | :material-check-bold: | 69.5.0 | | | +| wrapt | | :material-check-bold: | :material-check-bold: | 1.17.0.dev3 | | | +| zstandard | [:simple-github:](https://github.com/indygreg/python-zstandard/issues/231) | | | | | | + +[^1]: Rust library releasted on [crates.io](https://crates.io/crates/pyo3)