Add no-GIL interpreter support

Add `pytest-run-parallel` as dependency, test no-GIL interpreters in CI, and
mark Cython module as safe for freethreaded interpreters.
This commit is contained in:
Charlie Lin 2025-07-20 11:33:23 -04:00
parent 42f056f3cf
commit 6ced817616
7 changed files with 59 additions and 7 deletions

View file

@ -10,7 +10,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: ["ubuntu-latest", "windows-latest", "macos-latest"] os: ["ubuntu-latest", "windows-latest", "macos-latest"]
py: ["3.14-dev", "3.13", "3.12", "3.11", "3.10", "3.9", "3.8"] py: ["3.14", "3.14t", "3.13", "3.13t", "3.12", "3.11", "3.10", "3.9", "3.8"]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: Run test with Python ${{ matrix.py }} on ${{ matrix.os }} name: Run test with Python ${{ matrix.py }} on ${{ matrix.os }}
@ -41,12 +41,12 @@ jobs:
- name: Test (C extension) - name: Test (C extension)
shell: bash shell: bash
run: | run: |
pytest -v test PYTHON_GIL=0 pytest -v test
- name: Test (pure Python fallback) - name: Test (pure Python fallback)
shell: bash shell: bash
run: | run: |
MSGPACK_PUREPYTHON=1 pytest -v test PYTHON_GIL=0 MSGPACK_PUREPYTHON=1 pytest -v test
- name: build packages - name: build packages
shell: bash shell: bash

View file

@ -32,8 +32,9 @@ jobs:
uses: pypa/cibuildwheel@v2.23.3 uses: pypa/cibuildwheel@v2.23.3
env: env:
CIBW_TEST_REQUIRES: "pytest" CIBW_TEST_REQUIRES: "pytest"
CIBW_TEST_COMMAND: "pytest {package}/test" CIBW_TEST_COMMAND: "PYTHON_GIL=0 pytest {package}/test"
CIBW_SKIP: "pp* cp38-macosx_*" CIBW_SKIP: "pp* cp38-macosx_*"
CIBW_ENABLE: cpython-freerelease
- name: Build sdist - name: Build sdist
if: runner.os == 'Linux' && runner.arch == 'X64' if: runner.os == 'Linux' && runner.arch == 'X64'

View file

@ -1,5 +1,5 @@
# coding: utf-8 # coding: utf-8
#cython: embedsignature=True, c_string_encoding=ascii, language_level=3 #cython: embedsignature=True, c_string_encoding=ascii, language_level=3, freethreading_compatible=True
from cpython.datetime cimport import_datetime, datetime_new from cpython.datetime cimport import_datetime, datetime_new
import_datetime() import_datetime()

View file

@ -1,5 +1,5 @@
# coding: utf-8 # coding: utf-8
# cython: freethreading_compatible = True
from cpython cimport * from cpython cimport *
from cpython.bytearray cimport PyByteArray_Check, PyByteArray_CheckExact from cpython.bytearray cimport PyByteArray_Check, PyByteArray_CheckExact
from cpython.datetime cimport ( from cpython.datetime cimport (

View file

@ -1,5 +1,5 @@
# coding: utf-8 # coding: utf-8
# cython: freethreading_compatible = True
from cpython cimport * from cpython cimport *
cdef extern from "Python.h": cdef extern from "Python.h":
ctypedef struct PyObject ctypedef struct PyObject

View file

@ -1 +1,2 @@
Cython~=3.1.1 Cython~=3.1.1
pytest-run-parallel[psutil]

View file

@ -0,0 +1,50 @@
#!/usr/bin/env python3
from concurrent.futures import ThreadPoolExecutor
from msgpack import Packer
import threading
def run_threaded(
func,
num_threads=8,
pass_count=False,
pass_barrier=False,
outer_iterations=1,
prepare_args=None,
):
"""Runs a function many times in parallel"""
for _ in range(outer_iterations):
with ThreadPoolExecutor(max_workers=num_threads) as tpe:
if prepare_args is None:
args = []
else:
args = prepare_args()
if pass_barrier:
barrier = threading.Barrier(num_threads)
args.append(barrier)
if pass_count:
all_args = [(func, i, *args) for i in range(num_threads)]
else:
all_args = [(func, *args) for i in range(num_threads)]
try:
futures = []
for arg in all_args:
futures.append(tpe.submit(*arg))
finally:
if len(futures) < num_threads and pass_barrier:
barrier.abort()
for f in futures:
f.result()
def test_multithread_packing():
output = []
test_data = "abcd" * 10_000_000
packer = Packer()
def closure(b):
data = packer.pack(test_data)
output.append(data)
b.wait()
run_threaded(closure, num_threads=10, pass_barrier=True, pass_count=False)