Skip to content

Commit ba574eb

Browse files
Merge pull request #226 from oscarbenjamin/pr_freethreading
Add freethreading build and tests
2 parents c3baa5b + b84f13c commit ba574eb

File tree

4 files changed

+97
-46
lines changed

4 files changed

+97
-46
lines changed

.github/workflows/buildwheel.yml

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,36 @@ jobs:
8686
os: [ubuntu-24.04, ubuntu-24.04-arm, windows-2019, macos-13, macos-14]
8787
# This list to be kept in sync with cibuildwheel config
8888
# and python-requires in pyproject.toml.
89-
python-version: ['3.11', '3.12', '3.13'] # , 'pypy3.10']
89+
python-version: ['3.11', '3.12', '3.13', '3.13t'] # , 'pypy3.10']
90+
91+
# XXX: Maybe this exclude can be removed when changing from
92+
# Quansight-Labs/setup-python...
93+
exclude:
94+
- os: ubuntu-24.04-arm
95+
python-version: '3.13t'
9096

9197
steps:
92-
- uses: actions/setup-python@v5
98+
# Quansight-labs/setup-python is needed for 3.13t until a new version of
99+
# actions/setup-python (>5.4.0) is released.
100+
#
101+
# https://github.com/actions/setup-python/pull/973
102+
- uses: Quansight-Labs/setup-python@v5
93103
with:
94104
python-version: ${{ matrix.python-version }}
95105
- uses: actions/download-artifact@v4
96106
with:
97107
name: wheels-${{ matrix.os }}
98108
path: wheelhouse
99109
- run: pip install --no-index --find-links wheelhouse python_flint
110+
111+
# Check if the GIL is disabled in the free-threading build after import.
112+
- run: |
113+
python --version --version
114+
which python
115+
python -c "import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"
116+
python -c "import sys; print(getattr(sys, '_is_gil_enabled', lambda: True)())"
117+
python -c "import sys; import flint; print(getattr(sys, '_is_gil_enabled', lambda: True)())"
118+
100119
- run: python -m flint.test --verbose
101120

102121
# On new enough Ubuntu we can build against the system deb.
@@ -227,37 +246,6 @@ jobs:
227246
- run: pip install -r requirements-dev.txt
228247
- run: bin/coverage.sh
229248

230-
# On new enough Ubuntu we can build against the system deb.
231-
test_freethreaded:
232-
name: Free-threaded ${{ matrix.python-version }} on ${{ matrix.os }}
233-
runs-on: ${{ matrix.os }}
234-
strategy:
235-
fail-fast: false
236-
matrix:
237-
os: [ubuntu-24.04]
238-
python-version: ['3.13', '3.14-dev']
239-
steps:
240-
- uses: actions/checkout@v4
241-
# Can't use actions/setup-python
242-
# https://github.com/actions/setup-python/issues/771
243-
# deadsnakes only works for Ubuntu...
244-
- uses: deadsnakes/[email protected]
245-
with:
246-
python-version: ${{ matrix.python-version }}
247-
nogil: true
248-
- run: |
249-
python --version --version
250-
which python
251-
python -c "import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"
252-
python -c "import sys; print(sys._is_gil_enabled())"
253-
- run: sudo apt-get update
254-
- run: sudo apt-get install libflint-dev
255-
# Need Cython master until 3.1 is released
256-
- run: pip install git+https://github.com/cython/cython.git@master
257-
- run: pip install -r requirements-dev.txt
258-
- run: pip install --no-build-isolation .
259-
- run: python -m flint.test --verbose
260-
261249
# Run SymPy test suite against python-flint master
262250
test_sympy:
263251
name: Test SymPy ${{ matrix.sympy-version }}

meson.build

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ add_project_arguments(
8383
language : 'cython'
8484
)
8585

86+
# Enable free-threading if Cython is new enough. The check should be
87+
# >= 3.1.0a1 but meson gets confused by the a1 alpha release suffix.
88+
# so we go with >= 3.1 (which will be correct once 3.1 is released).
89+
cy = meson.get_compiler('cython')
90+
if cy.version().version_compare('>=3.1')
91+
message('Enabling freethreading')
92+
add_project_arguments('-Xfreethreading_compatible=true', language : 'cython')
93+
else
94+
message('Disabling freethreading')
95+
endif
96+
8697
if get_option('coverage')
8798
add_project_arguments('-X', 'linetrace=True', language : 'cython')
8899
add_project_arguments('-DCYTHON_TRACE=1', language : 'c')

pyproject.toml

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ version = "0.7.0"
66
requires-python = ">= 3.11"
77
authors = [
88
{name = "Fredrik Johansson", email = "[email protected]"},
9+
{name = "Oscar Benjamin", email = "[email protected]"},
910
]
1011
license = {text = "MIT"}
1112
classifiers = [
@@ -27,16 +28,16 @@ content-type = "text/markdown"
2728
# Minimum build requirements tested in CI need to be kept in sync with the
2829
# versions in requires below so that they are tested.
2930
#
30-
# The upper cap on Cython is speculative but we may as well cap it given that
31-
# it is only a build requirement and that it is not uncommon for newer Cython
32-
# versions to break the build. For example Cython 3.0 broke the build and then
33-
# Cython 3.1 broke the build again so python-flint 0.6.0 did not have any upper
34-
# cap but it should have had cython>=3.0,<3.1 i.e. precisely one minor release
35-
# of Cython works. In future we could contemplate not having an upper cap but
36-
# until we have actually witnessed a Cython 3.x release that does not break the
37-
# build we should keep the upper cap.
31+
# To support the freethreaded build (CPython 3.13t) the alpha release 3.1.0a1
32+
# of Cython is needed as a minimum requirement. It is possible that future
33+
# versions of Cython might still work but typically a Cython release breaks
34+
# something in the build of python-flint so we pin the exact version here.
3835
#
39-
requires = ["meson-python>=0.13", "cython>=3.0.11,<=3.1.0a1"]
36+
# Apart from the freethreading build any Cython version from 3.0.11 onwards is
37+
# fine. It is not possible to have a separate version constraint here for the
38+
# freethreading build only though.
39+
#
40+
requires = ["meson-python>=0.13", "cython==3.1.0a1"]
4041
build-backend = "mesonpy"
4142

4243
[tool.cython-lint]
@@ -81,12 +82,10 @@ package = "flint"
8182
[tool.cibuildwheel]
8283
# requires-python needs to keep in sync with this and also the list of Python
8384
# versions the wheels are tested against in CI.
84-
build = "cp311-* cp312-* cp313-*" # pp311-*"
85+
build = "cp311-* cp312-* cp313-* cp313t-*" # pp311-*"
8586
skip = "*-win32 *-manylinux_i686 *-musllinux_*"
8687

87-
# This is needed for free-threaded wheels:
88-
# build = "cp313t-*"
89-
# free-threaded-support = true
88+
free-threaded-support = true
9089

9190
manylinux-x86_64-image = "manylinux2014"
9291
manylinux-aarch64-image = "manylinux_2_28"

src/flint/test/test_all.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,10 +1612,12 @@ def set_bad2():
16121612
M6 = M6_copy
16131613
assert M6.nullspace() == (M([[1,15,1],[0,0,0],[0,0,0]],17).transpose(), 1)
16141614

1615+
16151616
def test_nmod_series():
16161617
# XXX: currently no code in nmod_series.pyx
16171618
pass
16181619

1620+
16191621
def test_arb():
16201622
A = flint.arb
16211623
assert A(3) > A(2.5)
@@ -1626,6 +1628,7 @@ def test_arb():
16261628
assert A(3) != A(2)
16271629
assert not (A("1.1") == A("1.1"))
16281630

1631+
16291632
def test_pickling():
16301633
objects = [
16311634
flint.fmpz(1),
@@ -4735,6 +4738,54 @@ def test_fq_default_poly():
47354738
assert raises(lambda: f.pow_trunc(-1, 5), ValueError)
47364739

47374740

4741+
def test_python_threads():
4742+
#
4743+
# https://github.com/flintlib/python-flint/issues/224
4744+
#
4745+
# XXX: This test crashes under the free-threading mode because of memory
4746+
# corruption.
4747+
#
4748+
# It is not clear if this should be fixed or if mutating
4749+
# matrices/polynomials that are shared between multiple threads should just
4750+
# be disallowed.
4751+
#
4752+
4753+
# Skip the test on the free-threaded build...
4754+
import sys
4755+
if sys.version_info[:2] >= (3, 13) and not sys._is_gil_enabled():
4756+
return
4757+
4758+
from threading import Thread
4759+
4760+
iterations = 10**5
4761+
threads = 3 + 1
4762+
size = 3
4763+
M = flint.fmpz_mat([[0]*size for _ in range(size)])
4764+
4765+
def set_values():
4766+
for i in range(iterations // 5):
4767+
i = random.randrange(M.nrows())
4768+
j = random.randrange(M.ncols())
4769+
if random.uniform(0, 1) > 0.5:
4770+
# Bigger than 2**62:
4771+
M[i,j] = 10**128
4772+
else:
4773+
# Smaller than 2**62:
4774+
M[i,j] = 0
4775+
4776+
def get_dets():
4777+
for i in range(iterations):
4778+
M.det()
4779+
4780+
threads = [Thread(target=set_values) for _ in range(threads-1)]
4781+
threads.append(Thread(target=get_dets))
4782+
4783+
for t in threads:
4784+
t.start()
4785+
for t in threads:
4786+
t.join()
4787+
4788+
47384789
def test_all_tests():
47394790
test_funcs = {f for name, f in globals().items() if name.startswith("test_")}
47404791
untested = test_funcs - set(all_tests)
@@ -4813,5 +4864,7 @@ def test_all_tests():
48134864

48144865
test_pickling,
48154866

4867+
test_python_threads,
4868+
48164869
test_all_tests,
48174870
]

0 commit comments

Comments
 (0)