…ace respx
PR #60 landed the cancellable-sleep fix in Transport._flush_loop and
expected CI wall-clock to drop to 3-5 minutes. The first green run
on PR #60 (PR #60 run #1) actually took 9m 47s — the test step
dominated by a retry storm:
Request failed (attempt 5/11), retrying in 8.46s: ConnectError
Request failed (attempt 6/11), retrying in 9.16s: ConnectError
...
Circuit breaker OPEN. Batch of 10 events will be re-queued.
Root cause: `tests/conftest.py:reset_runtime` teardown nulled the
runtime reference WITHOUT calling `runtime.shutdown()`. The
transport flush thread therefore kept running across tests, the
buffer drained through httpx with no respx context active, and the
xdist workers spent the next 9 minutes retry-sending the buffer
against the real (unreachable in CI) backend. `_retry_with_backoff
(max_retries=10, max_delay=10s)` is 65s of pure sleep per failed
batch, and with 4 xdist workers and many buffered batches this
multiplied into 9m 47s — i.e. a CI-noise fix that hid a deeper
lifecycle bug.
Pre-fix CI was already paying this cost (5s shutdown-sleep × 200+
tests ≈ 17 min of teardown per Python leg); the retry storm was
always there but masked by the dominant 5s cost. PR #60's 5s fix
exposed it.
Fix: add `flush: bool = True` to both `Transport.stop()` and
`NullRunRuntime.shutdown()`. When False, the transport thread is
cancelled WITHOUT a final `_do_flush()` / `_persist_to_wal()`.
`tests/conftest.py:reset_runtime` teardown now calls
`inst.shutdown(flush=False)` before nilling the reference. This
makes the conftest teardown a true no-op for the buffer — the test
that wrote the events is responsible for asserting on what it
cared about. The production default (`flush=True`) is preserved,
so the `nullrun.shutdown()` audit contract ("drain in-flight
events") is unchanged.
Pins:
* `tests/test_transport.py::test_stop_flush_false_skips_final_flush
` — buffers an event, calls `stop(flush=False)` with no
respx active, asserts the call returns in <1s AND the buffer
is left untouched. Pre-fix this would have hung for 65s+ on
the first retry.
* `tests/test_init_contract.py::TestShutdownFlushKwarg::
test_runtime_shutdown_flush_false_skips_final_flush` — same
contract at the `NullRunRuntime` level: `shutdown(flush=False
)` propagates the `flush=False` flag to
`Transport.stop()`.
Public API additions:
* `Transport.stop(timeout=10.0, flush: bool = True)` — `flush
=False` is the new flag.
* `NullRunRuntime.shutdown(flush: bool = True)` — propagates.
* `nullrun.shutdown(timeout=2.0, flush: bool = True)` — passes
`flush` through to the runtime.
No on-wire or production behaviour change. CI step is expected to
drop from ~9m 47s (PR #60 run #1) to ~30-60s on the next run.
release(0.13.5)
This is the 0.13.5 perf release — pairs the LangChain elif-chain
fix from 0.13.4 with a CI/runtime perf pass that the audit revealed.
What's in this PR
Three commits on top of
origin/master(note: the 0.13.4elif-chain fix already landed on master as PR #59, so the matching
c17a582version-bump commit is intentionally not cherry-picked — its changelog text was already on master and would have
been a no-op):
6ec7671— perf(ci):Transport._flush_loopswappedtime.sleepforthreading.Event.waitsoruntime.shutdown()returns in ms, not the fullflush_interval(5s default). Pin:tests/test_transport.py::test_stop_interrupts_flush_sleepuses 30s
flush_intervaland assertsstop() < 5s.84fc9d5— docs: removedocs/drift.mdanddocs/sdk-v3-migration-gaps.md(drift content already inCHANGELOG.mdand the per-release__version__.pyheaders).99bc197— chore(release): bump 0.13.4 → 0.13.5(
pyproject.toml+src/nullrun/__version__.py) with a fullchangelog block describing the perf fix + CI hygiene.
CI hygiene (in the perf commit)
setup-pythonaction:cache: pip+cache-dependency-path: pyproject.toml— saves ~60-90s ofcold install per matrix leg.
strategy.fail-fast: trueon the test matrix.pip install "pytest-xdist>=3.6"+pytest -n autoso thesuite uses all runner cores.
xdistalso added to[project.optional-dependencies.dev]for local parity.pyproject.toml: dropped the global-qfromaddoptssoCI logs surface the full
PASSEDline per test;--tb=shortkeeps tracebacks compact.-n autostays inthe workflow (not
addopts) so a developer runningpytest tests/test_x.pylocally gets a single process.Wire format
No change. The default
FlushConfigis unchanged (5sflush_interval, 50batch_size); production flush cadence isidentical. The fix only shortens the worst-case shutdown latency.
Compatibility
Backends on 1.0.0 keep working unchanged. No
SDK_MIN_VERSIONbump. Recommended upgrade path: 0.13.4 → 0.13.5.
Validation
test_stop_interrupts_flush_sleeppasses in 0.26s (was 30s).test_legacy_key_warning— 0.19s (was 5.18s).test_remote_states_race×4 — no 5.0s teardowns.test_state_compare_case_insensitive×10 — all <0.2s.ruff checkclean on changed files.mypyclean ontransport.py.Branch / commit policy
release/0.13.5cut fromorigin/master(notfrom a prior
release/*, per repo convention).Co-Authored-Bytrailers.user.name/user.emailfrom local git config.