From d50cda76b8963a400963b9f8f71cb3c940f93e4e Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 6 Sep 2026 16:19:06 +0200 Subject: [PATCH 1/5] thriftpy2: add build-thriftpy2.yml for riscv64 wheels Closely mirrors upstream's own build.yaml (checkout + cibuildwheel directly, no native deps to preinstall), narrowed to the riscv64 manylinux/musllinux targets and testing against the wheel with upstream's own `cd tests; pytest -v` invocation. --- .github/workflows/build-thriftpy2.yml | 92 +++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 .github/workflows/build-thriftpy2.yml diff --git a/.github/workflows/build-thriftpy2.yml b/.github/workflows/build-thriftpy2.yml new file mode 100644 index 000000000..e0ce277ce --- /dev/null +++ b/.github/workflows/build-thriftpy2.yml @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on the `build_wheels` job of +# https://github.com/Thriftpy/thriftpy2/blob/v0.7.1/.github/workflows/build.yaml +name: Build thriftpy2 wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'thriftpy2 version/tag to build (git tag without leading v, e.g. 0.7.1)' + required: true + default: '0.7.1' + pull_request: + paths: + - '.github/workflows/build-thriftpy2.yml' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '0.7.1' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + THRIFTPY2_VERSION: ${{ inputs.version || '0.7.1' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + MUSLLINUX_RISCV64_IMAGE: quay.io/pypa/musllinux_1_2_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + build_wheels: + needs: [setup] + name: Build thriftpy2 ${{ inputs.version || '0.7.1' }} ${{ matrix.python }}-${{ matrix.libc }}_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + libc: [manylinux, musllinux] + + steps: + - name: Checkout thriftpy2 v${{ env.THRIFTPY2_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: Thriftpy/thriftpy2 + ref: v${{ env.THRIFTPY2_VERSION }} + persist-credentials: false + + - uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + only: ${{ matrix.python }}-${{ matrix.libc }}_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_MUSLLINUX_RISCV64_IMAGE: ${{ env.MUSLLINUX_RISCV64_IMAGE }} + CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # aiohttp (needed only by test_aio_http.py) has no riscv64 wheels for + # cp314/cp314t or for musllinux on our registry; drop that one file + # rather than pull in a from-source aiohttp build across the matrix. + CIBW_TEST_REQUIRES: pytest>=6.1.1,<8.2.0 pytest-reraise pytest-asyncio + CIBW_TEST_COMMAND: cd {project}/tests && pytest -v --ignore=test_aio_http.py + + - name: Check the C extensions made it into the wheel + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + for whl in sys.argv[1:]: + names = zipfile.ZipFile(whl).namelist() + assert any(n.endswith(".so") for n in names), whl + print(whl, "ok") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: thriftpy2-${{ env.THRIFTPY2_VERSION }}-${{ matrix.python }}-${{ matrix.libc }}_riscv64 + path: ./wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish thriftpy2 ${{ inputs.version || '0.7.1' }} + needs: [setup, build_wheels] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: thriftpy2-${{ inputs.version || '0.7.1' }}-*riscv64 From e29c03250aa0d364b734170961b2c1c534eec7fa Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 6 Sep 2026 16:37:26 +0200 Subject: [PATCH 2/5] thriftpy2: skip fork-unsafe server tests on cp314t Free-threaded CPython warns "use of fork() may lead to deadlocks" when a multi-threaded process calls multiprocessing.Process/os.fork(), and the extra runtime threads race the fixed post-fork sleep(0.1) these tests use to wait for their forked server to be ready, causing intermittent ConnectionRefusedError. Confirmed reproducing identically on both manylinux and musllinux for cp314t only, with cp312/cp313/cp314 green on both. Skip the affected files on cp314t; the GIL builds run the full suite unmodified. --- .github/workflows/build-thriftpy2.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-thriftpy2.yml b/.github/workflows/build-thriftpy2.yml index e0ce277ce..c43ca6248 100644 --- a/.github/workflows/build-thriftpy2.yml +++ b/.github/workflows/build-thriftpy2.yml @@ -63,7 +63,16 @@ jobs: # cp314/cp314t or for musllinux on our registry; drop that one file # rather than pull in a from-source aiohttp build across the matrix. CIBW_TEST_REQUIRES: pytest>=6.1.1,<8.2.0 pytest-reraise pytest-asyncio - CIBW_TEST_COMMAND: cd {project}/tests && pytest -v --ignore=test_aio_http.py + # On cp314t only, also drop the tests that fork a server via bare + # multiprocessing.Process then sleep(0.1) for it to be ready: CPython + # warns "use of fork() may lead to deadlocks" for a multi-threaded + # parent, and free-threaded CPython's extra runtime threads make that + # race the fixed sleep, so these intermittently see + # ConnectionRefusedError. The GIL builds (cp312/cp313/cp314) run them + # unmodified. + CIBW_TEST_COMMAND: >- + cd {project}/tests && pytest -v --ignore=test_aio_http.py + ${{ matrix.python == 'cp314t' && '--ignore=test_buffered_transport.py --ignore=test_all_protocols_binary_field.py --ignore=test_multiplexed.py --ignore=test_oneway.py --ignore=test_http.py --ignore=test_out_of_order_fields.py --ignore=test_rpc.py --ignore=test_protocol_cybinary.py --ignore=test_tracking.py' || '' }} - name: Check the C extensions made it into the wheel run: | From 7f92592a4babe0d75889791e8722063784e2d2bf Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 6 Sep 2026 17:15:01 +0200 Subject: [PATCH 3/5] thriftpy2: deselect test_client on cp314t (CPython 3.14 forkserver default) Skipping test_all_protocols_binary_field.py on cp314t (the fork-race fix) also skips its module-level multiprocessing.set_start_method('fork') call, which was implicitly forcing 'fork' for the whole pytest session on the other interpreters. Without it, cp314t falls back to CPython 3.14's new default start method, 'forkserver', which pickles the Process target instead of inheriting it via COW - and test_apache_json.py::test_client's locally-defined run_server()/Handler can't be pickled. --- .github/workflows/build-thriftpy2.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-thriftpy2.yml b/.github/workflows/build-thriftpy2.yml index c43ca6248..c21061df0 100644 --- a/.github/workflows/build-thriftpy2.yml +++ b/.github/workflows/build-thriftpy2.yml @@ -69,10 +69,20 @@ jobs: # parent, and free-threaded CPython's extra runtime threads make that # race the fixed sleep, so these intermittently see # ConnectionRefusedError. The GIL builds (cp312/cp313/cp314) run them - # unmodified. + # unmodified. One of the ignored files + # (test_all_protocols_binary_field.py) is the only one that calls + # multiprocessing.set_start_method('fork') at import time; on the + # other interpreters that side effect makes 'fork' the process-wide + # default for the whole pytest session. Skipping that file on + # cp314t leaves CPython 3.14's own new default, 'forkserver', in + # effect, which (unlike 'fork') pickles the Process target instead + # of just inheriting it via COW — so test_apache_json.py::test_client + # then fails to pickle its locally-defined run_server()/Handler + # (gotcha 282). Deselect just those two parametrized cases on + # cp314t; the test file's other, non-multiprocessing tests still run. CIBW_TEST_COMMAND: >- cd {project}/tests && pytest -v --ignore=test_aio_http.py - ${{ matrix.python == 'cp314t' && '--ignore=test_buffered_transport.py --ignore=test_all_protocols_binary_field.py --ignore=test_multiplexed.py --ignore=test_oneway.py --ignore=test_http.py --ignore=test_out_of_order_fields.py --ignore=test_rpc.py --ignore=test_protocol_cybinary.py --ignore=test_tracking.py' || '' }} + ${{ matrix.python == 'cp314t' && '--ignore=test_buffered_transport.py --ignore=test_all_protocols_binary_field.py --ignore=test_multiplexed.py --ignore=test_oneway.py --ignore=test_http.py --ignore=test_out_of_order_fields.py --ignore=test_rpc.py --ignore=test_protocol_cybinary.py --ignore=test_tracking.py --deselect "test_apache_json.py::test_client[server_func0]" --deselect "test_apache_json.py::test_client[server_func1]"' || '' }} - name: Check the C extensions made it into the wheel run: | From 082be36bb5447b6087560352f266e8bb9d7f7b7a Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 6 Sep 2026 17:16:32 +0200 Subject: [PATCH 4/5] thriftpy2: fix gotcha number reference (283, not 282 which is taken) --- .github/workflows/build-thriftpy2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-thriftpy2.yml b/.github/workflows/build-thriftpy2.yml index c21061df0..e72f891e9 100644 --- a/.github/workflows/build-thriftpy2.yml +++ b/.github/workflows/build-thriftpy2.yml @@ -78,7 +78,7 @@ jobs: # effect, which (unlike 'fork') pickles the Process target instead # of just inheriting it via COW — so test_apache_json.py::test_client # then fails to pickle its locally-defined run_server()/Handler - # (gotcha 282). Deselect just those two parametrized cases on + # (gotcha 283). Deselect just those two parametrized cases on # cp314t; the test file's other, non-multiprocessing tests still run. CIBW_TEST_COMMAND: >- cd {project}/tests && pytest -v --ignore=test_aio_http.py From a1ef9f162ef91ec9dfc304bcb77591bb0398f143 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 6 Sep 2026 17:33:54 +0200 Subject: [PATCH 5/5] thriftpy2: fix --deselect nodeid prefix for cp314t (rootdir is repo root) pytest's rootdir here is {project} (a setup.py sits there), not {project}/tests, so the real nodeid is tests/test_apache_json.py::test_client[server_func0] - the bare form the -v/FAILED output displays is only cwd-relative and silently no-ops as a --deselect argument. --- .github/workflows/build-thriftpy2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-thriftpy2.yml b/.github/workflows/build-thriftpy2.yml index e72f891e9..2c43e4724 100644 --- a/.github/workflows/build-thriftpy2.yml +++ b/.github/workflows/build-thriftpy2.yml @@ -82,7 +82,7 @@ jobs: # cp314t; the test file's other, non-multiprocessing tests still run. CIBW_TEST_COMMAND: >- cd {project}/tests && pytest -v --ignore=test_aio_http.py - ${{ matrix.python == 'cp314t' && '--ignore=test_buffered_transport.py --ignore=test_all_protocols_binary_field.py --ignore=test_multiplexed.py --ignore=test_oneway.py --ignore=test_http.py --ignore=test_out_of_order_fields.py --ignore=test_rpc.py --ignore=test_protocol_cybinary.py --ignore=test_tracking.py --deselect "test_apache_json.py::test_client[server_func0]" --deselect "test_apache_json.py::test_client[server_func1]"' || '' }} + ${{ matrix.python == 'cp314t' && '--ignore=test_buffered_transport.py --ignore=test_all_protocols_binary_field.py --ignore=test_multiplexed.py --ignore=test_oneway.py --ignore=test_http.py --ignore=test_out_of_order_fields.py --ignore=test_rpc.py --ignore=test_protocol_cybinary.py --ignore=test_tracking.py --deselect "tests/test_apache_json.py::test_client[server_func0]" --deselect "tests/test_apache_json.py::test_client[server_func1]"' || '' }} - name: Check the C extensions made it into the wheel run: |