Commit 7c6be63
committed
fix: bound _expire_heap growth from re-adds and DRY test helpers
Address remaining review feedback on #1718.
**Re-add heap growth (Copilot inline at L100, real bug separate from
the orphan).** ``_async_add``'s cap check is gated on ``is_new``, but
a peer that just replays cached records with a different ``created``/
TTL hits the ``is_new == False`` path: no cap check, no
``_total_records`` change — yet the changed ``when`` makes
``_expirations.get(record) != when`` true, so a fresh ``(when,
record)`` tuple is heappushed while the prior tuple stays behind as
stale. ``_expire_heap`` grows unbounded between cleanups even though
``cache`` and ``_total_records`` stay flat. At ~10k pps for 10 s
that's ~1 M stale heap entries × ~100 B ≈ 100 MB transient.
Factor the rebuild check (``len(heap) > _MIN_SCHEDULED_RECORD_EXPIRATION
and len(heap) > 2 * len(expirations)`` → filter-and-heapify) into a
new ``_maybe_rebuild_heap`` cdef helper. Call it from three sites:
- ``_async_add`` immediately after the conditional heappush, so a
re-add flood is bounded at every step (amortized O(1) per push,
O(N) rebuild fires at most once per N stale pushes)
- ``async_expire`` after the pop loop, replacing the existing inline
rebuild — same behavior, slightly more aggressive (uses post-pop
heap length instead of pre-pop), no test regression
- (removed from ``_async_evict_oldest`` — the eager rebuild in
``_async_add`` keeps the heap bounded before eviction is ever
triggered, so the eviction-side call was dead code)
Cython codegen: the rebuild call from ``_async_add`` only fires when
the conditional heappush already fired, so the hot path on duplicate
re-adds (same ``when``) is unchanged. The check inside
``_maybe_rebuild_heap`` is two ``len()`` calls + an integer compare —
score-0 in ``cython -a``.
**Test DRY-up.** Introduce a module-level ``_addr(name, idx, ...)``
helper that builds the boilerplate ``DNSAddress(name, _TYPE_A,
_CLASS_IN, ttl, bytes((idx & 0xFF, (idx >> 8) & 0xFF, 0, 1)), created
=...)`` so each new test reads top-down instead of being dominated by
constructor noise. Test docstrings collapsed to one sentence per
``CLAUDE.md``.
**New regression test:** ``test_cache_re_add_flood_does_not_grow_heap_unbounded``
adds 200 records below the cap, then replays them through 10 cycles
of varying TTLs (2000 stale pushes total) and asserts the heap stays
near the rebuild threshold. The previous code would have grown it to
~11x the record count; the helper keeps it within ``2 * expirations``.
**Removed test:** ``test_cache_eviction_rebuilds_heap_when_mostly_stale``
— its premise (build up stale entries, then trigger eviction-side
rebuild) is no longer reachable now that ``_async_add`` rebuilds
proactively. The same code path is covered by the re-add flood test.
**Existing tests updated:** ``test_cache_heap_multi_name_cleanup`` and
``test_cache_heap_pops_order`` previously asserted pre-cleanup heap
size (``min_records_to_cleanup + 5``). With proactive rebuild firing
inside ``_async_add``, the heap is cleaned up earlier and the post-
cleanup invariant (``1 + 5`` after ``async_expire``) still holds, but
the intermediate snapshot is no longer accurate. Replaced the stale
intermediate assertion with a comment.1 parent 931358a commit 7c6be63
3 files changed
Lines changed: 99 additions & 255 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
70 | | - | |
| 70 | + | |
71 | 71 | | |
72 | 72 | | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
73 | 76 | | |
74 | 77 | | |
75 | 78 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
113 | | - | |
114 | 113 | | |
115 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
116 | 121 | | |
117 | 122 | | |
118 | 123 | | |
| |||
122 | 127 | | |
123 | 128 | | |
124 | 129 | | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
133 | 138 | | |
134 | | - | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
135 | 152 | | |
136 | 153 | | |
137 | 154 | | |
| |||
142 | 159 | | |
143 | 160 | | |
144 | 161 | | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | 162 | | |
153 | 163 | | |
154 | 164 | | |
| |||
190 | 200 | | |
191 | 201 | | |
192 | 202 | | |
193 | | - | |
| 203 | + | |
194 | 204 | | |
195 | 205 | | |
196 | 206 | | |
197 | | - | |
198 | 207 | | |
199 | 208 | | |
200 | 209 | | |
201 | 210 | | |
202 | 211 | | |
203 | 212 | | |
204 | | - | |
205 | | - | |
206 | | - | |
207 | | - | |
| 213 | + | |
| 214 | + | |
208 | 215 | | |
209 | 216 | | |
210 | 217 | | |
211 | 218 | | |
212 | | - | |
213 | | - | |
214 | | - | |
215 | | - | |
216 | | - | |
217 | | - | |
218 | | - | |
219 | | - | |
220 | | - | |
221 | | - | |
222 | | - | |
223 | | - | |
224 | | - | |
225 | | - | |
226 | | - | |
227 | | - | |
228 | | - | |
229 | | - | |
| 219 | + | |
230 | 220 | | |
231 | 221 | | |
232 | 222 | | |
| |||
0 commit comments