From 2009b146a355add3af554aaf6d014e1e9a60389f Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 20 May 2024 13:56:51 -0400 Subject: [PATCH 1/5] gh-119241: Add HOWTO for free-threaded C API extensions Some sections adapted from https://github.com/Quansight-Labs/free-threaded-compatibility/ written by Nathan Goldbaum. Co-authored-by: Nathan Goldbaum --- Doc/howto/free-threading-extensions.rst | 230 ++++++++++++++++++++++++ Doc/howto/index.rst | 1 + 2 files changed, 231 insertions(+) create mode 100644 Doc/howto/free-threading-extensions.rst diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst new file mode 100644 index 00000000000000..c4ff4ef89e8900 --- /dev/null +++ b/Doc/howto/free-threading-extensions.rst @@ -0,0 +1,230 @@ +.. highlight:: c + +.. _freethreading-extensions-howto: + +****************************************** +C API Extension Support for Free Threading +****************************************** + +Starting with the 3.13 release, CPython has experimental support for running +with the :term:`global interpreter lock` (GIL) disabled in a configuration +called :term:`free threading`. This document describes how to adapt C API +extensions to support free threading. + + +Identifying the Free Threaded Build in C +======================================== + +The CPython C API exposes the ``Py_GIL_DISABLED`` macro: in the freethreaded +build it's defined to ``1``, and in the regular build it's not defined. +You can use it to enable code that only runs under the free-threaded build:: + + #ifdef Py_GIL_DISABLED + /* code that only runs in the free-threaded build */ + #endif + +Module Initialization +===================== + +Extension modules need to explicitly indicate that they support running with +the GIL disabled; otherwise importing the extension will raise a warning and +enable the GIL at runtime. + +There are two ways to indicate that an extension module supports running with +the GIL disabled depending on whether the extension uses multi-phase or +single-phase initialization. + +Multi-Phase Initialization +.......................... + +Extensions that use multi-phase initialization (i.e., +:c:func:`PyModuleDef_Init`) should add a :c:data:`Py_mod_gil` slot in the +module definition. If your extension supports older versions of CPython, +you should guard the slot with a :c:data:`PY_VERSION_HEX` check. + +:: + + static struct PyModuleDef_Slot module_slots[] = { + ... + #if PY_VERSION_HEX >= 0x030D0000 + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + #endif + {0, NULL} + }; + + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_slots = module_slots, + ... + }; + + +Single-Phase Initialization +........................... + +Extensions that use single-phase initialization (i.e., +:c:func:`PyModule_Create`) should call :c:func:`PyUnstable_Module_SetGIL` to +indicate that they support running with the GIL disabled. The function is +only defined in the freethreaded build, so you should guard the call with +``#ifdef Py_GIL_DISABLED`` to avoid compilation errors in the regular build. + +:: + + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + ... + }; + + PyMODINIT_FUNC + PyInit_mymodule(void) + { + PyObject *m = PyModule_Create(&moduledef); + if (m == NULL) { + return NULL; + } + #ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); + #endif + return m; + } + + +General API Guidelines +====================== + +Most of the C API is thread-safe, but there are some exceptions. + +* **Struct Fields**: Accessing struct fields directly is not thread-safe if + the field may be concurrently modified. +* **Macros**: Accessor macros like :c:macro:`PyList_GET_ITEM` and + :c:macro:`PyList_SET_ITEM` do not perform any error checking or locking. + These macros are not thread-safe if the container object may be modified + concurrently. +* **Borrowed References**: C API functions that return + :term:`borrowed references ` may not be thread-safe if + the containing object is modified concurrently. See the section on + :ref:`borrowed references ` for more information. + + +Container Thread Safety +....................... + +Containers like :c:struct:`PyListObject`, +:c:struct:`PyDictObject`, and :c:struct:`PySetObject` perform internal locking +in the free-threaded build. For example, the :c:func:`PyList_Append` will +lock the list before appending an item. + + +Borrowed References +=================== + +.. _borrowed-references: + +Some C API functions return :term:`borrowed references `. +These APIs are not thread-safe if the containing object is modified +concurrently. For example, it's not safe to use :c:func:`PyList_GetItem` +if the list may be modified concurrently. + +The following table lists some borrowed reference APIs and their replacements +that return :term:`strong references `. + ++-----------------------------------+-----------------------------------+ +| Borrowed reference API | Strong reference API | ++===================================+===================================+ +| :c:func:`PyList_GetItem` | :c:func:`PyList_GetItemRef` | ++-----------------------------------+-----------------------------------+ +| :c:func:`PyDict_GetItem` | :c:func:`PyDict_GetItemRef` | ++-----------------------------------+-----------------------------------+ +| :c:func:`PyDict_GetItemWithError` | :c:func:`PyDict_GetItemRef` | ++-----------------------------------+-----------------------------------+ +| :c:func:`PyDict_GetItemString` | :c:func:`PyDict_GetItemStringRef` | ++-----------------------------------+-----------------------------------+ +| :c:func:`PyDict_SetDefault` | :c:func:`PyDict_SetDefaultRef` | ++-----------------------------------+-----------------------------------+ +| :c:func:`PyDict_Next` | no direct replacement | ++-----------------------------------+-----------------------------------+ +| :c:func:`PyWeakref_GetObject` | :c:func:`PyWeakref_GetRef` | ++-----------------------------------+-----------------------------------+ +| :c:func:`PyWeakref_GET_OBJECT` | :c:func:`PyWeakref_GetRef` | ++-----------------------------------+-----------------------------------+ +| :c:func:`PyImport_AddModule` | :c:func:`PyImport_AddModuleRef` | ++-----------------------------------+-----------------------------------+ + +Not all APIs that return borrowed references are problematic. For +example, :c:func:`PyTuple_GetItem` is safe because tuples are immutable. +Similarly, not all uses of the above APIs are problematic. For example, +:c:func:`PyDict_GetItem` is often used for parsing keyword argument +dictionaries in function calls; those keyword argument dictionaries are +effectively private (not accessible by other threads), so using borrowed +references in that context is safe. + +Some of these functions were added in Python 3.13. You can use the +`pythoncapi-compat `_ package +to provide implementations of these functions for older Python versions. + + +Memory Allocation APIs +====================== + +Python's memory management C API provides functions in three different +:ref:`allocation domains `: "raw", "mem", and "object". +For thread-safety, the free-threaded build requires that only Python objects +are allocated using the object domain, and that all Python object are +allocated using that domain. This differes from the prior Python versions, +where this was only a best practice and not a hard requirement. + +.. note:: + + Search for uses of :c:func:`PyObject_Malloc` in your + extension and check that the allocated memory is used for Python objects. + Use :c:func:`PyMem_Malloc` to allocate buffers instead of + :c:func:`PyObject_Malloc`. + + +Limited C API +============= + +The free-threaded build does not currently support the +:ref:`limited C API ` or the stable ABI. If you use +`setuptools `_ to build +your extension and currenlty set ``py_limited_api=True`` you can use +``py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")`` to opt out +of the limited API when building with the free-threaded build. + + +Thread State and GIL APIs +========================= + +Python provides a set of functions and macros to manage thread state and the +GIL, such as: + +* :c:func:`PyGILState_Ensure` and :c:func:`PyGILState_Release` +* :c:func:`PyEval_SaveThread` and :c:func:`PyEval_RestoreThread` +* :c:macro:`Py_BEGIN_ALLOW_THREADS` and :c:macro:`Py_END_ALLOW_THREADS` + +These functions should still be used in the free-threaded build to manage +thread state even when the :term:`GIL` is disabled. For example, if you +create a thread outside of Python, you must call :c:func:`PyGILState_Ensure` +before calling into the Python API to ensure that the thread has a valid +Python thread state. + +You should continue to call :c:func:`PyEval_SaveThread` or +:c:macro:`Py_BEGIN_ALLOW_THREADS` around blocking operations, such as I/O or +lock acquisitions, to allow other threads to run the +:term:`cyclic garbage collector `. + + +Protecting Internal Extension State +=================================== + +Your extension may have internal state that was previously protected by the +GIL. You may need to add locking to protect this state. The approach will +depend on your extension, but some common patterns include: + +* **Caches**: global caches are a common source of shared state. Consider + using a lock to protect the cache or disabling it in the free-threaded build + if the cache is not critical for performance. +* **Global State**: global state may need to be protected by a lock or moved + to thread local storage. C11 and C++11 provide the ``thread_local`` or + ``_Thread_local`` for + `thread-local storage `_. diff --git a/Doc/howto/index.rst b/Doc/howto/index.rst index 065071e39a06c5..a1f17eca906291 100644 --- a/Doc/howto/index.rst +++ b/Doc/howto/index.rst @@ -34,4 +34,5 @@ Currently, the HOWTOs are: isolating-extensions.rst timerfd.rst mro.rst + free-threading-extensions.rst From 61e7dbc56bc4c8b5363064f0127360bb38d661ad Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Fri, 31 May 2024 14:32:07 -0400 Subject: [PATCH 2/5] Update Doc/howto/free-threading-extensions.rst Co-authored-by: Jelle Zijlstra --- Doc/howto/free-threading-extensions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst index c4ff4ef89e8900..1607a9a4ce3f8d 100644 --- a/Doc/howto/free-threading-extensions.rst +++ b/Doc/howto/free-threading-extensions.rst @@ -187,7 +187,7 @@ Limited C API The free-threaded build does not currently support the :ref:`limited C API ` or the stable ABI. If you use `setuptools `_ to build -your extension and currenlty set ``py_limited_api=True`` you can use +your extension and currently set ``py_limited_api=True`` you can use ``py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")`` to opt out of the limited API when building with the free-threaded build. From 5bc873ac1837a905d631ce9feff346e31961c0fb Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 3 Jun 2024 14:28:34 -0400 Subject: [PATCH 3/5] Update docs --- Doc/howto/free-threading-extensions.rst | 63 ++++++++++++++++++------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst index 1607a9a4ce3f8d..d1e0edac3c65a0 100644 --- a/Doc/howto/free-threading-extensions.rst +++ b/Doc/howto/free-threading-extensions.rst @@ -12,10 +12,10 @@ called :term:`free threading`. This document describes how to adapt C API extensions to support free threading. -Identifying the Free Threaded Build in C +Identifying the Free-Threaded Build in C ======================================== -The CPython C API exposes the ``Py_GIL_DISABLED`` macro: in the freethreaded +The CPython C API exposes the ``Py_GIL_DISABLED`` macro: in the free-threaded build it's defined to ``1``, and in the regular build it's not defined. You can use it to enable code that only runs under the free-threaded build:: @@ -65,7 +65,7 @@ Single-Phase Initialization Extensions that use single-phase initialization (i.e., :c:func:`PyModule_Create`) should call :c:func:`PyUnstable_Module_SetGIL` to indicate that they support running with the GIL disabled. The function is -only defined in the freethreaded build, so you should guard the call with +only defined in the free-threaded build, so you should guard the call with ``#ifdef Py_GIL_DISABLED`` to avoid compilation errors in the regular build. :: @@ -94,8 +94,8 @@ General API Guidelines Most of the C API is thread-safe, but there are some exceptions. -* **Struct Fields**: Accessing struct fields directly is not thread-safe if - the field may be concurrently modified. +* **Struct Fields**: Accessing fields in Python C API objects or structs + directly is not thread-safe if the field may be concurrently modified. * **Macros**: Accessor macros like :c:macro:`PyList_GET_ITEM` and :c:macro:`PyList_SET_ITEM` do not perform any error checking or locking. These macros are not thread-safe if the container object may be modified @@ -181,17 +181,6 @@ where this was only a best practice and not a hard requirement. :c:func:`PyObject_Malloc`. -Limited C API -============= - -The free-threaded build does not currently support the -:ref:`limited C API ` or the stable ABI. If you use -`setuptools `_ to build -your extension and currently set ``py_limited_api=True`` you can use -``py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")`` to opt out -of the limited API when building with the free-threaded build. - - Thread State and GIL APIs ========================= @@ -228,3 +217,45 @@ depend on your extension, but some common patterns include: to thread local storage. C11 and C++11 provide the ``thread_local`` or ``_Thread_local`` for `thread-local storage `_. + + +Building Extensions for the Free-Threaded Build +=============================================== + +C API extensions need to be built specifically for the free-threaded build. +The wheels, shared libraries, and binaries are indicated by a ``t`` suffix. + +* `pypa/manylinux `_ supports the + free-threaded build, with the ``t`` suffix, such as ``python3.13t``. +* `pypa/cibuildwheel `_ supports the + free-threaded build on Linux and Windows if you set + `CIBW_FREE_THREADED_SUPPORT `_. + +Limited C API and Stable ABI +............................ + +The free-threaded build does not currently support the +:ref:`limited C API ` or the stable ABI. If you use +`setuptools `_ to build +your extension and currently set ``py_limited_api=True`` you can use +``py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")`` to opt out +of the limited API when building with the free-threaded build. + +.. note:: + You will need to build separate wheels specifically for the free-threaded + build. If you currently use the stable ABI, you can continue to build a + single wheel for multiple non-free-threaded Python versions. + + +Windows +....... + +Due to a limitation of the official Windows installer, you will need to +manually define ``Py_GIL_DISABLED=1`` when building extensions from source. + + +macOS +..... + +The offical macOS binaries do not currently support the free-threaded build +as of 3.13b1. You will need to build Python from source. From 063280875dbc3d19fc0731e2bc2fdc89631e68a4 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 17 Jun 2024 12:49:11 -0400 Subject: [PATCH 4/5] Update Doc/howto/free-threading-extensions.rst Co-authored-by: Itamar Oren --- Doc/howto/free-threading-extensions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst index d1e0edac3c65a0..abeccab42b905b 100644 --- a/Doc/howto/free-threading-extensions.rst +++ b/Doc/howto/free-threading-extensions.rst @@ -235,7 +235,7 @@ Limited C API and Stable ABI ............................ The free-threaded build does not currently support the -:ref:`limited C API ` or the stable ABI. If you use +:ref:`Limited C API ` or the stable ABI. If you use `setuptools `_ to build your extension and currently set ``py_limited_api=True`` you can use ``py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")`` to opt out From 9d7edf1f4eaaf76a8085582a8877f4a92d26449f Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 17 Jun 2024 12:51:26 -0400 Subject: [PATCH 5/5] 3.13b2 has a free-threaded build on macOS --- Doc/howto/free-threading-extensions.rst | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Doc/howto/free-threading-extensions.rst b/Doc/howto/free-threading-extensions.rst index abeccab42b905b..080170de1e5d16 100644 --- a/Doc/howto/free-threading-extensions.rst +++ b/Doc/howto/free-threading-extensions.rst @@ -228,7 +228,7 @@ The wheels, shared libraries, and binaries are indicated by a ``t`` suffix. * `pypa/manylinux `_ supports the free-threaded build, with the ``t`` suffix, such as ``python3.13t``. * `pypa/cibuildwheel `_ supports the - free-threaded build on Linux and Windows if you set + free-threaded build if you set `CIBW_FREE_THREADED_SUPPORT `_. Limited C API and Stable ABI @@ -252,10 +252,3 @@ Windows Due to a limitation of the official Windows installer, you will need to manually define ``Py_GIL_DISABLED=1`` when building extensions from source. - - -macOS -..... - -The offical macOS binaries do not currently support the free-threaded build -as of 3.13b1. You will need to build Python from source.