Skip to content

test: make two GIL heartbeat workloads clear the 30 ms floor in release builds - #394

Merged
iceboundrock merged 1 commit into
mainfrom
autoforge/180
Nov 11, 2026
Merged

iceboundrock merged 1 commit into
mainfrom
autoforge/180

Conversation

@iceboundrock

Copy link
Copy Markdown
Contributor

Why

test_gil_release.py refuses to judge a call shorter than MIN_ELAPSED_S (30 ms), because the heartbeat thread cannot tick enough in that time even with the GIL released. CI builds the extension in debug mode, where every workload is slow enough. In a release build, the build users install and the one a release-build test job (#328) would run, some calls finished below the floor and the self-check failed. The GIL was never the problem.

What still failed on current main

The issue was filed before b5e02bd changed several workloads, so I rebuilt with maturin develop --release and measured each test (5 runs, 8-core Linux):

test release, before release, after
test_count_releases_gil 7–10 ms (fails) 124–143 ms
test_search_pattern_count_parquet_parallel_releases_gil 17–18 ms (fails) 110–123 ms
test_pivot_releases_gil 55–58 ms unchanged
test_insert_row_releases_gil 56–63 ms unchanged
test_delete_row_releases_gil 47–58 ms unchanged
test_modify_row_releases_gil 48–55 ms unchanged

The last four already clear the floor, so they are left alone. The other 13 heartbeat tests take 70–320 ms.

The change

Both failing calls split their work across the fixture's eight row groups, so they run faster with each core up to eight, and a bigger machine would shrink them further. The replacement workloads do work that does not split that way:

  • count: counts the rows past a shift(1) derive and filter. The shift is a window over the whole table in one partition, which count() has to evaluate. The test is still the control for count() running detached.
  • parallel pattern count: reads a one-row-group copy of the fixture (new module fixture single_rg_parquet_path), so the per-row-group scan is a single task. It also adds & (r.value >= 0.0) to every step, which takes the scan off the starts_with fast path (prefix matching on url alone) and onto per-row evaluation of every step's predicate over two columns. The terms match every row, so the test also asserts the count equals the plain funnel's. These predicates stay on the parallel Parquet path: over the eight-row-group file they take ~27 ms, while the DataFusion fallback takes ~100 ms even on in-memory rows.

The module docstring now says that workloads must clear the floor in a release build too, and on machines with more cores.

Deriving the floor from a calibration run would not fix this: a call as short as the old count call leaves the heartbeat only a handful of ticks, too close to the at most ~2 it makes while the GIL is held.

Testing

  • maturin develop --release, then pytest py-ltseq/tests/test_gil_release.py, 5 runs: 19 passed each time (timings above).
  • maturin develop (debug, as CI builds), then pytest py-ltseq/tests/test_gil_release.py -v: 19 passed in 29.7 s; the slowest test took 3.9 s.
  • The full suite was not run locally (test-only change to one file); CI runs it.

Closes #180

…lds (#180)

In a release build, test_count_releases_gil (7-10 ms) and
test_search_pattern_count_parquet_parallel_releases_gil (17-18 ms) finished
under the 30 ms MIN_ELAPSED_S floor, so the heartbeat self-check failed
before it could assess the GIL. Both calls split their work across the
fixture's eight row groups, so they get faster with each core up to eight.

count() now counts rows past a shift window, which runs over the whole table
in one partition. The parallel pattern scan now reads a one-row-group copy of
the file, where it runs as one task, and adds a `value` term to every step,
which takes it off the starts_with fast path onto per-row predicate
evaluation, still on the parallel Parquet path.

The other four tests the issue lists (pivot, insert, delete, modify) already
clear the floor on current main after their workloads changed in b5e02bd.
@iceboundrock

Copy link
Copy Markdown
Contributor Author

AI Code Review — Round 1

Reviewed HEAD: c6cdb565c8232591adbc9b2a5f5658acc426e72d against base main (merge base 65c59243d789b573d49c4309e7a07d14208f52b5)

Findings

None.

Spec

The revised workloads address the release-build timing failures without lowering the 30 ms floor or weakening the heartbeat assertion. The count workload requires window evaluation, and the pattern workload remains on the Parquet scan path while avoiding row-group parallel speedup. All six originally reported workloads cleared the floor locally.

Standards

Consistent with AGENTS.md, CLAUDE.md, the existing test structure, and ADR 0016. This test-only change introduces no public behavior requiring changelog, docstring, or stub updates. No reviewed files were modified.

Assessment

No correctness, security, or actionable coverage defects found. The change is narrowly scoped, preserves the GIL-release detector, and includes a semantic equivalence assertion for the heavier pattern predicates.

Observations

The supplied PR context matches the bound HEAD and base main. All supplied CI checks were pending; no controller validation commands were configured. Local timing measurements establish margin on this machine, not a universal hardware guarantee.

Verification

Read the full bound diff, issue, PR description, repository guidance, and related Python/Rust execution paths. The affected module passed all 19 tests with the existing debug extension in 27.89 seconds. Loading the existing release extension directly without replacing files produced 19 passes in 3.04 seconds, then another 19 passes in 3.14 seconds. Instrumented release calls measured count at 121.0 ms, pattern count at 112.3 ms, pivot at 55.2 ms, insert at 57.3 ms, delete at 55.5 ms, and modify at 50.5 ms. git diff --check passed. Final worktree status was clean and HEAD remained the bound commit.

Summary

No findings; another fix round is not needed. Debug and release testing supports the intended fix. Full CI results remain pending.

Needs another fix round: NO

@iceboundrock
iceboundrock merged commit 3e32380 into main Nov 11, 2026
4 checks passed
@iceboundrock
iceboundrock deleted the autoforge/180 branch November 11, 2026 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test_gil_release heartbeat self-check fails under release builds (call too fast to observe)

1 participant