Commit 6d742a8
test(internal): add lifecycle stress / fuzzing harness for PeriodicThread (#18183)
## Description
Adds `tests/internal/test_periodic_stress.py` — a randomized lifecycle
fuzzer for `PeriodicThread` that interleaves
create / start / stop / join / awake / drop-last-ref / fork / gc /
thread-churn across a pool of `PeriodicThread` and `PeriodicService`
objects.
This is the harness that surfaced #17707 and #18040
(`PeriodicThread.awake()` blocking forever after `stop()`).
### What's in the file
- `test_periodic_thread_lifecycle_stress` — the randomized fuzzer.
Default budget kept short for CI (`DD_STRESS_ITERS=200`, ~10s).
Documented env vars for soaks (`DD_STRESS_ITERS`, `DD_STRESS_SECONDS`,
`DD_STRESS_SEED`, `DD_STRESS_POOL`, `DD_STRESS_FORK_EVERY`,
`DD_STRESS_TRACE_FILE`). Seed is printed on every run so a failing
random seed can be pinned with `DD_STRESS_SEED=N`.
- `test_periodic_thread_concurrent_dealloc_race` — focused regression
for #17485 (refcount TOCTOU between `std::thread` creation and the
lambda acquiring the GIL).
- `test_periodic_thread_stop_without_join_then_fork_repeat` — focused
regression for #16955 (`pthread_t` recycling after stop-without-join
then fork).
### How to run it when modifying the periodic thread framework
The module docstring documents the exact commands. tl;dr:
```
# ~30s soak
DD_STRESS_ITERS=10000 scripts/run-tests -- -- tests/internal/test_periodic_stress.py
# 120s wall-clock soak (recommended under ASan/TSan)
DD_STRESS_SECONDS=120 scripts/run-tests -- -- tests/internal/test_periodic_stress.py
# Reproduce a failing seed (the seed is printed to stderr by every run)
DD_STRESS_SEED=12345 scripts/run-tests -- -- tests/internal/test_periodic_stress.py
```
## Testing
All three tests pass locally on Python 3.13 in the testrunner image:
```
tests/internal/test_periodic_stress.py::test_periodic_thread_lifecycle_stress PASSED
tests/internal/test_periodic_stress.py::test_periodic_thread_concurrent_dealloc_race PASSED
tests/internal/test_periodic_stress.py::test_periodic_thread_stop_without_join_then_fork_repeat PASSED
```
## Risks
None — test-only addition. Existing test discovery and CI suites are
unchanged.
`changelog/no-changelog` — test-only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: brettlangdon <brett.langdon@datadoghq.com>
Co-authored-by: erwan.viollet <erwan.viollet@datadoghq.com>1 parent 5ea55b2 commit 6d742a8
1 file changed
Lines changed: 462 additions & 0 deletions
0 commit comments