Skip to content

Commit e7f14c7

Browse files
etrclaude
andcommitted
Merge TASK-032: Thread-safety contract stress test (DR-008)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents 2f7b4ea + 5aa69d6 commit e7f14c7

7 files changed

Lines changed: 587 additions & 10 deletions

File tree

specs/architecture/11-decisions/DR-008.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@
1919
- `http_request` is single-threaded per request.
2020
- `http_response` is exclusively owned (value type).
2121

22+
**Verification (TASK-032):**
23+
- `test/integ/threadsafety_stress.cpp` — stress test binary `threadsafety_stress` runs 16 concurrent curl clients for 60 seconds (override via `HTTPSERVER_STRESS_SECONDS=N`), each request randomly invoking `register_path`, `unregister_path`, `block_ip`, and `unblock_ip` against the running `webserver` from inside a handler thread. A duplicate-registration from a handler throws `std::invalid_argument` rather than causing a data race.
24+
- CI coverage: the `build-type: tsan` matrix entry in `.github/workflows/verify-build.yml` compiles with `-fsanitize=thread` and runs `make check`, which automatically picks up `threadsafety_stress` as a registered `check_PROGRAMS` entry — no separate workflow wiring is needed.
25+
- Opt-in negative test `stop_from_handler_deadlocks_as_documented` (enabled via `HTTPSERVER_RUN_STOP_FROM_HANDLER=1`) forks a child process that calls `stop()` from inside a handler. A non-zero child exit (libmicrohttpd self-join abort, exit code 42) or a 5-second parent timeout (exit code 43) both count as positive observation of the documented deadlock contract. These exit codes serve as regression sentinels.
26+
2227
---

specs/tasks/M5-routing-lifecycle/TASK-032.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
Verify the documented thread-safety contract: `webserver` public methods are reentrant from inside a handler, except `stop()` and `~webserver()` which deadlock by design.
99

1010
**Action Items:**
11-
- [ ] Write a stress test (`test/threadsafety_stress.cpp`) that runs N concurrent handlers, each randomly invoking `register_resource`, `block_ip`, `unblock_ip`, `unregister_resource` against the running `webserver`.
12-
- [ ] Run under ThreadSanitizer in CI; assert no data races.
13-
- [ ] Add a separate test that calls `stop()` from inside a handler thread and asserts deadlock-detection (or simply documents the timeout); skip the test by default in CI but make it runnable on demand to validate the contract.
14-
- [ ] Document the deadlock case in `webserver::stop()` Doxygen.
11+
- [x] Write a stress test (`test/integ/threadsafety_stress.cpp`) that runs 16 concurrent curl clients, each request randomly invoking `register_path` (the non-deprecated successor to `register_resource`), `unregister_path`, `block_ip`, `unblock_ip` against the running `webserver` from inside an on_get lambda handler. Default duration 60 s; override with `HTTPSERVER_STRESS_SECONDS=N`.
12+
- [x] Run under ThreadSanitizer in CI; the existing `build-type: tsan` matrix entry in `.github/workflows/verify-build.yml` invokes `make check`, which auto-picks up every new `check_PROGRAMS` entry — no workflow edit needed.
13+
- [x] Add a separate test (`stop_from_handler_deadlocks_as_documented`) that calls `stop()` from inside a handler thread; opt-in via `HTTPSERVER_RUN_STOP_FROM_HANDLER=1`. The test forks a child so the abort/deadlock does not crash the test binary; either a non-zero child exit (libmicrohttpd self-join abort) or a 5 s timeout counts as positive observation of the contract.
14+
- [x] Document the deadlock case in `webserver::stop()` and `~webserver()` Doxygen.
1515

1616
**Dependencies:**
1717
- Blocked by: TASK-027, TASK-031
@@ -26,4 +26,4 @@ Verify the documented thread-safety contract: `webserver` public methods are ree
2626
**Related Requirements:** PRD §2 NFR (concurrency)
2727
**Related Decisions:** DR-008, §5.1, §9 testing item 6, AR-006
2828

29-
**Status:** Not Started
29+
**Status:** Done

specs/tasks/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Nominally: **13 sequential tasks**, each S–XL. Most other tasks parallelize of
114114
| TASK-029 | Naming consistency — `stop_and_wait`, `block_ip`/`unblock_ip` | M5 | Done | TASK-014 |
115115
| TASK-030 | `_handler` suffix renames + `explicit` constructor | M5 | Done | TASK-014 |
116116
| TASK-031 | Handler error-propagation contract (DR-009) | M5 | Done | TASK-027, TASK-030 |
117-
| TASK-032 | Thread-safety contract stress test (DR-008) | M5 | Not Started | TASK-027, TASK-031 |
117+
| TASK-032 | Thread-safety contract stress test (DR-008) | M5 | Done | TASK-027, TASK-031 |
118118
| TASK-033 | `create_webserver` builder cleanup | M5 | Not Started | TASK-006, TASK-014 |
119119
| TASK-034 | Build-flag-independent public API + `webserver::features()` | M5 | Not Started | TASK-003, TASK-019, TASK-033 |
120120
| TASK-035 | Smart-pointer `register_ws_resource` overloads | M5 | Not Started | TASK-014, TASK-034 |

0 commit comments

Comments
 (0)