@@ -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