Skip to content

Commit 8b478dc

Browse files
etrclaude
andcommitted
TASK-056: formally defer bench_route_lookup criterion to TASK-053
The acceptance criterion requiring `bench_route_lookup` to show no regression worse than 2x on the cache-miss radix path cannot be verified in TASK-056 because `test/bench_route_lookup.cpp` does not exist and `lookup_v2` is not yet wired into dispatch (that is TASK-053's scope). - Add an explicit DEFERRED note under TASK-056's acceptance criteria in v2-deferred-backlog-plan.md recording the rationale and the handoff. - Add a reminder under TASK-053's action items to verify the 2x budget when creating bench_route_lookup.cpp. - Update route-table.md to make the bench_route_lookup reference prospective rather than dangling ("once TASK-053 lands"). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8dfb558 commit 8b478dc

2 files changed

Lines changed: 18 additions & 4 deletions

File tree

specs/architecture/04-components/route-table.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ A `route_entry` carries:
2121

2222
**Prefix-vs-exact collision detection:** registering an exact route at a path that already has a prefix terminus (or vice versa) throws `std::invalid_argument` at registration time rather than silently double-registering. The cache key `(method, path)` cannot distinguish the two kinds at lookup time, so the conflict is rejected at the source. The guard probes both storage locations (`exact_routes_` and the radix tree's `exact_terminus_` / `prefix_terminus_`) before any mutation, so the atomicity contract — "a failed registration leaves the table exactly as it was" — still holds.
2323

24-
**Hash-flooding immunity (CWE-407):** the radix tree's per-segment children are kept in `std::map` rather than `std::unordered_map`. URL path segments are attacker-controlled, and neither libc++ nor libstdc++ seed `std::hash<std::string>` by default — a crafted sibling-key corpus can degrade `std::unordered_map::find` from O(1) amortized to O(n) per probe, opening an algorithmic-complexity DoS vector. The `std::map` (red-black tree) gives O(log n) worst case with no hashing in the loop. The transparent comparator `std::less<>` lets the hot-path lookup pass `std::string_view` keys directly without constructing a temporary `std::string` per probe. Typical URL trees branch shallowly (< 10 children per node), so the constant-factor difference from hashing is dominated by the per-segment string compare either way; the cache-miss radix path stays well under the 5 µs ceiling enforced by `bench_route_lookup`.
24+
**Hash-flooding immunity (CWE-407):** the radix tree's per-segment children are kept in `std::map` rather than `std::unordered_map`. URL path segments are attacker-controlled, and neither libc++ nor libstdc++ seed `std::hash<std::string>` by default — a crafted sibling-key corpus can degrade `std::unordered_map::find` from O(1) amortized to O(n) per probe, opening an algorithmic-complexity DoS vector. The `std::map` (red-black tree) gives O(log n) worst case with no hashing in the loop. The transparent comparator `std::less<>` lets the hot-path lookup pass `std::string_view` keys directly without constructing a temporary `std::string` per probe. Typical URL trees branch shallowly (< 10 children per node), so the constant-factor difference from hashing is dominated by the per-segment string compare either way; the cache-miss radix path stays well under the 5 µs ceiling (to be enforced by `test/bench_route_lookup.cpp` once TASK-053 lands and wires `lookup_v2` into dispatch).
2525

2626
**Future evolution:** if `std::map` probe cost dominates measured lookup time at high fanout, switching to an in-tree small-vector flat_map remains an internal-only optimization. v2.0 commits only to the *outer shape* (three-tier with cache), not the per-node container choice.
2727

specs/tasks/v2-deferred-backlog-plan.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,13 @@ LRU cache + radix scan) is not realised end-to-end.
6666
/ `webserver_routes.cpp`.)
6767
- [ ] Run `test/bench_hook_overhead.cpp` and a new
6868
`test/bench_route_lookup.cpp` to confirm the cache-hit path is in the
69-
expected 100ns ballpark and the radix tier is in the µs range.
69+
expected 100ns ballpark and the radix tier is in the µs range. When
70+
creating `bench_route_lookup.cpp` also verify the TASK-056 acceptance
71+
criterion: the `std::map`-based radix node container introduced by
72+
TASK-056 must show no regression worse than 2× on the cache-miss radix
73+
path compared to the former `std::unordered_map` baseline (the 2× budget
74+
was formally deferred from TASK-056 to this task because `lookup_v2` was
75+
not yet wired into dispatch when TASK-056 landed).
7076
- [ ] Drop the "TODO(Cycle K): rename route_cache_v2 → route_lru_cache"
7177
comment in `webserver_impl.hpp:202` and do the rename now that v1 is
7278
gone.
@@ -245,9 +251,17 @@ TASK-027 cleanup:
245251
**Acceptance Criteria:**
246252
- `bench_route_lookup` shows no regression worse than 2× on the
247253
cache-miss radix path.
254+
**DEFERRED to TASK-053**`test/bench_route_lookup.cpp` does not
255+
exist yet; TASK-053 already owns creating that benchmark as part of
256+
wiring `lookup_v2` into the dispatch hot path. The 2× budget
257+
established here applies to the `std::map`-based container swap and
258+
must be verified by TASK-053's implementer against the TASK-056
259+
baseline. Creating the benchmark in TASK-056 would pre-empt TASK-053's
260+
scope and would benchmark a lookup path (`lookup_v2`) that is not yet
261+
wired into dispatch.
248262
- New regression test passes.
249-
- A 60-second adversarial-segment stress run completes without dispatch
250-
latency spikes > 10× baseline.
263+
- A 60-second adversarial-segment stress run completes without
264+
registration latency spikes > 10× baseline.
251265
- Routing architecture doc reflects the new container.
252266

253267
**Related Findings:** task-027 #14, task-027 #18

0 commit comments

Comments
 (0)