Skip to content

Commit c86afde

Browse files
committed
gotchas: add 568 - awscrt appexit TIMEOUT flake on cp314t
A subprocess-exit self-test's own hardcoded wall-clock TIMEOUT can fail only on cp314t, only on one version, with no code change behind it - free-threading's extra per-object overhead tips an existing margin rather than creating a new bug (the awscrt 0.37.0 test_appexit case investigated on PR #2278).
1 parent df92ba3 commit c86afde

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

‎skills/python-project-porting/references/gotchas-index.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,11 @@ The porting gotchas (548 of them) live in [`references/gotchas/`](gotchas/), spl
452452
cache-populating step on `ubuntu-latest` and hand the tarball to the riscv job, and patch the
453453
extracted sdist rather than the checkout when a target sits inside a submodule
454454
(the couchbase case).
455+
- **569** — A tag can check out clean and still hand a build literal Git LFS pointer stubs
456+
instead of real content, when upstream squash-merges former git submodules into the main
457+
tree without resolving their LFS objects first — verify by sha256 against the pointer's own
458+
`oid` before trusting a same-content fetch from the pre-merge submodule (the semgrep
459+
v1.177.0 case).
455460

456461
### cibuildwheel mechanics, the matrix & abi3 — [`gotchas/cibuildwheel-matrix-and-abi3.md`](gotchas/cibuildwheel-matrix-and-abi3.md)
457462

@@ -1177,6 +1182,11 @@ The porting gotchas (548 of them) live in [`references/gotchas/`](gotchas/), spl
11771182
and unresolved without riscv64 hardware to debug interactively — not scenario-specific to
11781183
whichever test happened to be running, and distinct from an already-open pgsql-hackers
11791184
riscv64/GCC memory-failures thread (that one is sporadic; this one is deterministic).
1185+
- **568** — A subprocess-exit self-test's hardcoded `TIMEOUT` failing only on `cp314t` for
1186+
one version, with byte-identical test source and no relevant code change across versions,
1187+
is free-threading's per-object overhead tipping an existing margin on a shared riscv64
1188+
runner (awscrt 0.37.0's `test_appexit`) — patch the timeout with headroom, don't skip the
1189+
test that proves the extension doesn't crash the interpreter on exit.
11801190

11811191
### Licensing & GPL sources — [`gotchas/licensing-and-gpl.md`](gotchas/licensing-and-gpl.md)
11821192

‎skills/python-project-porting/references/gotchas/test-failures-and-flakes.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ To pull up one entry: `grep -n '^N\. ' references/gotchas/test-failures-and-flak
6464
path is real but unresolved on riscv64 without hardware to reproduce interactively —
6565
confirmed not threading-specific (it crashes both the single- and multi-threaded variant
6666
of the same helper), diagnosis stops there honestly.
67+
- **568** — A subprocess-exit self-test's own hardcoded `TIMEOUT` failing only on `cp314t`,
68+
only on one version, with byte-identical test source across versions, is free-threading
69+
tipping an existing margin, not a new bug — patch the timeout with real headroom rather
70+
than skip the test.
6771

6872
---
6973

@@ -1318,3 +1322,46 @@ To pull up one entry: `grep -n '^N\. ' references/gotchas/test-failures-and-flak
13181322
the built `initdb` (gotcha 115's technique) to see whether the fault is in `initdb.c`
13191323
itself or in something it links against; try a newer PostgreSQL point release; try an
13201324
older/newer GCC than 14.3.1 in the manylinux image.
1325+
1326+
568. **A subprocess-exit self-test with its own hardcoded wall-clock `TIMEOUT` can fail only
1327+
on `cp314t`, only on one version, with no code change behind it — free-threading's extra
1328+
per-object overhead tips an existing margin, it doesn't create a new bug (the awscrt
1329+
0.37.0 `test_appexit` case).** awscrt's `test_appexit.TestAppExit.test_http` runs
1330+
`test/appexit_http.py` as a subprocess once per teardown stage and asserts each exits 0;
1331+
every wait in that script (HTTP connect, body callback, connection-shutdown future, both
1332+
`ClientBootstrap`/`EventLoopGroup` `shutdown_event.wait()` calls) shares one hardcoded
1333+
`TIMEOUT = 30.0`. On this repo's riscv64 runners, only the `cp314-cp314t` leg of 0.37.0
1334+
failed, at the `ClientBootstrapDone` stage, with `AssertionError: 0 != 1` from
1335+
`subprocess.Popen(...).returncode` — while the same leg of the immediately preceding
1336+
0.36.3/0.36.4 (built in the very same PR run) and the 0.37.0 cp311/cp313 legs all passed.
1337+
- **Rule out a version-specific regression before treating a fixed-timeout failure as a
1338+
real bug**: `git diff <old-tag> <new-tag> -- test/appexit_http.py test/test_appexit.py`
1339+
found *zero* diff across three released versions, and the only source changes at all
1340+
were trivial submodule bumps (`aws-c-common`, `aws-c-s3`, `aws-lc`) that don't touch
1341+
`aws-c-io`'s bootstrap/event-loop/host-resolver shutdown path. Byte-identical test
1342+
files plus an unchanged code path rules out "new test hit a new bug" and "this
1343+
version broke something" — the margin already existed.
1344+
- **Distinguish a hang from a timing margin by reading what the background threads were
1345+
doing when the timeout fired, not just that it fired.** The subprocess's own
1346+
`awscrt.io.init_logging(Trace, 'stdout')` output shares the same fd the test harness
1347+
captures, so on this repo's shared runners a concurrent native thread's writes land
1348+
byte-interleaved with the Python traceback (literally splitting one log line, e.g.
1349+
`...cache due to shutdown` becoming `...caTraceback...` then resuming
1350+
`...he due to shutdown`). Reading past the corruption showed the DNS resolver's own
1351+
maintenance loop re-resolving the request host roughly once a second for ~24 of the
1352+
30-second budget before it decided to kill its background thread, and only then did
1353+
the channel-bootstrap-release / event-loop-destroy sequence that fires
1354+
`shutdown_event` begin — all of it clean, no error, just late. That is a margin
1355+
problem (under 6s left for the rest of native teardown to reach Python), not a
1356+
deadlock.
1357+
- **Fix scope: patch the hardcoded timeout with `Upstream-Status: Inappropriate` and
1358+
real headroom, don't skip the test** — this is gotcha 316's "artificial test
1359+
limitation, not a real defect" case again, and patching-and-licensing.md lists a fixed
1360+
timeout as an explicitly justified patch reason. Skipping `test_appexit` outright would
1361+
have cost the one test in the suite that actually proves the package's native
1362+
extension doesn't crash the interpreter on process exit — the exact thing worth
1363+
shipping a wheel to verify. Tripling `TIMEOUT` (30s → 90s) keeps that coverage while
1364+
giving a busy shared riscv64 runner driving a free-threaded (no-GIL) interpreter —
1365+
whose per-object atomic refcounting adds measurable overhead to native-callback-into-
1366+
Python teardown chains like this one — comfortable room, and it still fails well
1367+
inside the job's own timeout if the shutdown genuinely never completes.

0 commit comments

Comments
 (0)