Skip to content

Commit db6769e

Browse files
authored
ENH: enable CPython free-threading support (#468)
- Test the package using `pytest-run-parallel`, which allows pytest suites to be run concurrently (per test). This analysis didn't throw any major concurrency-related issues, the only tests that were marked as thread-unsafe were related to memory usage checking and concurrent file overwriting. - Add a CI to run tests in parallel, as well to build and release free-threaded wheels - Mark each C module as free-threaded compatible
1 parent db7bcfc commit db6769e

File tree

8 files changed

+64
-2
lines changed

8 files changed

+64
-2
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ${{ matrix.os }}
88
strategy:
99
matrix:
10-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
10+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"]
1111
architecture: [x86, x64]
1212
os:
1313
[
@@ -34,6 +34,12 @@ jobs:
3434
python-version: ${{ matrix.python-version }}
3535
architecture: ${{ matrix.architecture }}
3636

37+
38+
- if: ${{ endsWith(matrix.python-version, 't') }}
39+
run: |
40+
pip install pytest-run-parallel
41+
echo "PYTEST_ADDOPTS=--parallel-threads=4" >> "$GITHUB_ENV"
42+
3743
- name: Install
3844
run: |
3945
pip install . -v
@@ -69,7 +75,8 @@ jobs:
6975
- name: Build wheels
7076
uses: pypa/[email protected]
7177
env:
72-
CIBW_SKIP: pp*
78+
CIBW_SKIP: "pp* *t-manylinux_i686 *t-musllinux_i686"
79+
CIBW_ENABLE: cpython-freethreading
7380

7481
- name: Store wheel artifacts
7582
uses: actions/upload-artifact@v4

bottleneck/conftest.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
import pytest
3+
4+
try:
5+
import pytest_run_parallel # noqa:F401
6+
7+
PARALLEL_RUN_AVAILABLE = True
8+
except Exception:
9+
PARALLEL_RUN_AVAILABLE = False
10+
11+
12+
def pytest_configure(config):
13+
if not PARALLEL_RUN_AVAILABLE:
14+
config.addinivalue_line(
15+
'markers',
16+
'parallel_threads(n): run the given test function in parallel '
17+
'using `n` threads.',
18+
)
19+
config.addinivalue_line(
20+
"markers",
21+
"thread_unsafe: mark the test function as single-threaded",
22+
)
23+
config.addinivalue_line(
24+
"markers",
25+
"iterations(n): run the given test function `n` times in each thread",
26+
)
27+
28+
29+
if not PARALLEL_RUN_AVAILABLE:
30+
@pytest.fixture
31+
def num_parallel_threads():
32+
return 1

bottleneck/src/move_template.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,11 @@ PyInit_move(void)
15191519
{
15201520
PyObject *m = PyModule_Create(&move_def);
15211521
if (m == NULL) return NULL;
1522+
1523+
#ifdef Py_GIL_DISABLED
1524+
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
1525+
#endif
1526+
15221527
import_array();
15231528
if (!intern_strings()) {
15241529
return NULL;

bottleneck/src/nonreduce_axis_template.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,11 @@ PyInit_nonreduce_axis(void)
10271027
{
10281028
PyObject *m = PyModule_Create(&nra_def);
10291029
if (m == NULL) return NULL;
1030+
1031+
#ifdef Py_GIL_DISABLED
1032+
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
1033+
#endif
1034+
10301035
import_array();
10311036
if (!intern_strings()) {
10321037
return NULL;

bottleneck/src/nonreduce_template.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,11 @@ PyInit_nonreduce(void)
345345
{
346346
PyObject *m = PyModule_Create(&nonreduce_def);
347347
if (m == NULL) return NULL;
348+
349+
#ifdef Py_GIL_DISABLED
350+
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
351+
#endif
352+
348353
import_array();
349354
if (!intern_strings()) {
350355
return NULL;

bottleneck/src/reduce_template.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,11 @@ PyInit_reduce(void)
19661966
{
19671967
PyObject *m = PyModule_Create(&reduce_def);
19681968
if (m == NULL) return NULL;
1969+
1970+
#ifdef Py_GIL_DISABLED
1971+
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
1972+
#endif
1973+
19691974
import_array();
19701975
if (!intern_strings()) {
19711976
return NULL;

bottleneck/tests/memory_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66

7+
@pytest.mark.thread_unsafe
78
@pytest.mark.skipif(
89
sys.platform.startswith("win"), reason="resource module not available on windows"
910
)

bottleneck/tests/test_template.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import os
22
import posixpath as path
3+
import pytest
34

45
from ..src.bn_template import make_c_files
56

67

8+
@pytest.mark.thread_unsafe
79
def test_make_c_files() -> None:
810
dirpath = os.path.join(os.path.dirname(__file__), "data/template_test/")
911
modules = ["test"]

0 commit comments

Comments
 (0)