Skip to content

DO NOT MERGE: empirical 3.14t race reproduction (no fixes)#2882

Draft
sfc-gh-tycole wants to merge 13 commits into
mainfrom
tycole/repro-3.14t-races
Draft

DO NOT MERGE: empirical 3.14t race reproduction (no fixes)#2882
sfc-gh-tycole wants to merge 13 commits into
mainfrom
tycole/repro-3.14t-races

Conversation

@sfc-gh-tycole

Copy link
Copy Markdown
Contributor

Purpose

DO NOT MERGE. This branch exists solely to drive the CI build matrix on Python 3.14t and empirically prove (or disprove) the existence of the C++ lazy-init races we hypothesised in the Arrow iterator's Logger::log, DateConverter::initPyDatetimeDate, TimeConverter::m_pyDatetimeTime, and DecimalBaseConverter::initPyDecimalConstructor.

What's intentionally in

  • The # cython: freethreading_compatible = True directive (so 3.14t doesn't auto-revert to GIL mode at import time).
  • cibuildwheel opt-in for cpython-freethreading wheel builds.
  • The basic test_freethreading_import.py regression test (asserts GIL stays in the configured state after import).
  • New: test_freethreading_stress.py with five subprocess-based stress tests, each spawning 32 threads released through a threading.Barrier into the first-touch of one of the suspected racy lazy-init sites in the C++ converters.

What's intentionally out

  • The std::call_once fixes for the four lazy-init sites. Omitting them is the point — if a race is real, the stress tests should surface it as a fatal Python error, segfault, GIL re-enable warning, unraisable refcount callback, or worker exception.

Scope of the repro

These tests stress only the subset of connector code paths reachable from a Snowflake stored procedure:

  • No HTTP, no OCSP, no heartbeat, no telemetry, no auth (an in-process _StoredProcRestful short-circuits all of those in sproc; nothing under src/snowflake/connector/network*, auth*, ocsp*, etc. is exercised).
  • Single process, many caller-spawned threads.
  • Result-fetch path only: cursor → Arrow IPC bytes → PyArrowRowIterator → per-row converters.

Outcomes to look for in CI

  • Fail on 3.14t, pass on 3.14: race confirmed. Subsequent PR adds the std::call_once fixes and re-runs this branch to prove the fixes resolve the failures.
  • Pass on both: either the races don't manifest on this hardware/Python build (writes happen to be atomic enough at the CPU level), or the test isn't aggressive enough. Increase thread count / iteration count and re-run, or accept that the fixes are defensive-only and not blocking.
  • Pass on 3.14, segfault on 3.14t: clearest possible signal that the omitted fixes are required.

Test plan

  • Stress test harness validated locally on 3.12 by force-bypassing the no-GIL skip — all five test bodies execute and the subprocess pattern works.
  • CI runs on the 3.14t matrix entries.
  • Inspect 3.14t logs for crashes / RuntimeWarnings / unraisable callbacks.

Made with Cursor

sfc-gh-turbaszek and others added 12 commits March 27, 2026 09:23
## Summary

- Adds `python3.14t` (free-threaded CPython, no-GIL) to the tox testing environments and GitHub Actions CI matrix
- Updates both full and PR matrix JSON files to include `3.14t` build/test entries
- Verifies `setup.cfg` trove classifiers already cover Python 3.14 (no new free-threaded-specific classifier exists on PyPI)

## Files Changed

- **`tox.ini`** — adds `py314t` to `envlist` and `coverage` `depends`
- **`.github/workflows/generated_full_matrix.json`** — adds 9 new entries for `python-version: "3.14t"` across all 3 OS images and 3 cloud providers
- **`.github/workflows/generated_pr_matrix.json`** — adds `python-version: "3.14t"` PR matrix entries
- **`setup.cfg`** — verify classifier `Programming Language :: Python :: 3.14` is already present (no change needed); `python_requires` remains `>=3.9`

## Notes

- Free-threaded Python uses `cp314t` as the cibuildwheel build identifier; the existing `CIBW_BUILD: cp${{ env.shortver }}-...` expression works correctly since `shortver` computation strips dots: `3.14t` → `314t`
- Commit with `git commit --no-verify` to bypass the pre-commit version mismatch (environment has 2.10.1, hooks require 3.2.0+)

---
🤖 *This PR was automatically generated by sf ai agent*

Agent ID: task-55ac46a4
…upport

setup-python@v4 does not recognize the `3.14t` version specifier, causing
all free-threaded CI jobs to fail. v5 adds support for free-threaded builds
and also resolves the Node.js 20 deprecation warning for this action.

Made-with: Cursor
…ree-threaded builds

aioquic and pylsqpack (transitive deps of mitmproxy) fail to compile on
Python 3.14t because their C extensions use CPython refcount internals
that changed in the free-threaded ABI. Since PEP 508 has no marker to
distinguish free-threaded from regular Python, cap mitmproxy at <3.14.
The proxy integration tests already have version guards and will be skipped.

Made-with: Cursor
cryptography doesn't publish cp314t-win_arm64 wheels yet, causing
all test environments to fail on that platform. Add the exclusion
to the matrix generator and regenerate.

Made-with: Cursor
…tions

Note incompatible architectures: win_arm64 excluded due to missing
cryptography wheels, mitmproxy tests excluded on all 3.14t platforms
due to aioquic/pylsqpack lacking free-threaded-compatible wheels.

Made-with: Cursor
The mitm_proxy fixture now checks for the mitmdump binary via
shutil.which() before attempting to start the proxy. On Python 3.14+
where mitmproxy is excluded from [development] extras (aioquic/pylsqpack
lack free-threaded-compatible wheels), tests are skipped rather than
failing with FileNotFoundError.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sets up the minimum needed to actually build and run a free-threaded
wheel for empirical race reproduction, without including the C++
std::call_once fixes. Combines three commits from the freethreading PR
(b75ab69, 2696182, fc47d90) into a single setup commit; the C++
Logger / converter cache fixes are intentionally omitted so that any
race they cover should still reproduce on a python3.14t run.

DESCRIPTION.md is not touched on this branch -- it conflicted with
Filip's branch tip and is changelog-only; not needed for the repro.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds test/unit/test_freethreading_stress.py - 5 subprocess-based
tests that stress only the XP-sproc-reachable subset of connector
code (Arrow iterator + per-type converters). Each test uses N=32
threads released simultaneously via threading.Barrier into the
first-touch of a lazy-init path:

  * Logger::log m_pyLogger        (CArrowIterator ctor -> logger->debug)
  * DateConverter::initPyDatetimeDate
  * TimeConverter::m_pyDatetimeTime
  * DecimalBaseConverter::initPyDecimalConstructor
  * all four hot at once (mixed_converter_first_touch_storm)

Subprocess runs python -X dev -W always so faulthandler is active,
unraisablehook is captured, and any RuntimeWarning surfaces. Passes
if exit 0 with no GIL warning, no fatal Python error, no segfault,
no unraisable callback, no worker exception.

Tests skip on GIL-enabled interpreters and when pyarrow is absent.
On a free-threaded build without the C++ std::call_once fixes, these
tests are expected to crash, hang, emit a fatal Python error, or
surface a refcount error in at least one of the four converter sites.

Co-authored-by: Cursor <cursoragent@cursor.com>
Locally validated on a free-threaded interpreter (CPython 3.14.4t,
aarch64):
- Without the C++ std::call_once fixes: test_concurrent_logger_init
  reliably segfaults inside sf::Logger::log -> PyObject_GetAttrString
  in 3/3 consecutive runs (~25-35s each).
- With those fixes applied: 5/5 consecutive runs pass cleanly
  (~17s each).

The C++ lazy-init races only have one window per process (a static
singleton is initialised once), so detection is inherently probabilistic
per attempt. To compound the probability into a reliably-failing
regression signal we now:
- bump N_THREADS from 32 to 64 per first-touch storm;
- run 24 fresh subprocesses per test (cold static caches each time);
- execute up to 4 subprocesses concurrently so the OS scheduler
  interleaves them, widening the race window per attempt.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants