| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Sorry, something went wrong.
There was a problem hiding this comment.
Sorry, something went wrong.
CI Test ResultsRun: #37912237452 | Commit: 45e2bd7 | Duration: 17m 14s (longest job)
Status Overview
Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled Summary: Total: 32 | Passed: 32 | Failed: 0 Updated: 2026-10-09 09:56:12 UTC |
Sorry, something went wrong.
Sorry, something went wrong.
|
Self-review. I did a thorough review of this PR. Findings and how each is resolved, fixes in 7c24b1c:
Ran: full gtestDebug; refCountGuard_ut in release, ASan and TSan; stringDictionary_ut and test_callTraceStorage under ASan and TSan; compileFuzzer; testDebug (228 tests, 0 failures). |
Sorry, something went wrong.
slotReferences() read a slot's active_ptr, then its outer_stack. A reentrant guard destructor moves the outer resource from outer_stack back to active_ptr, storing it before clearing the stack entry, so a scan that read active_ptr before the store and outer_stack after the clear saw the resource in neither place. A dictionary lookup interrupted by a signal handler's put() could therefore be missed by the targeted drain, and clearAll() could free storage the lookup was still using. Read active_ptr again after the stack; the ACQUIRE load of the cleared entry guarantees the second read sees the restored pointer. The same scan backs waitForRefCountToClear(), so its callers benefit too. Add RefCountGuard::isReferenced(), a single non-waiting scan. A buffer whose clearStandby() was skipped became the active buffer on the next rotate() with its old entries. A straggler can have inserted a key there after both copies of an earlier rotate(), with an id the active buffer has since assigned differently, and Phase 1's copyFrom() kept the stale id. StringDictionary now remembers the skipped buffer and rotate() retries the drain and clear before reusing it; it returns false if the buffer is still in use, which rotateDictsAndRun() reports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The re-read added to slotReferences() closed the miss across one nested guard ending, but not across one ending and the next starting: the outer resource moved outer_stack -> active_ptr -> outer_stack between the scanner's reads, so it could be absent from every location the scanner read although the outer guard held it throughout. Stop moving it. A reentrant guard now records its own resource in nested[depth - 1] (formerly outer_stack) and leaves active_ptr, which keeps the root guard's resource. Every protected resource stays in one location from its guard's construction to its destruction, so a single pass over active_ptr and nested[] cannot miss it, and the re-read is dropped. Nested guards no longer save and restore active_ptr, and the destructor and move assignment share release(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Self-review of the guard-scan fix. A nested guard stored its nested[] entry with RELEASE after count++, and the caller's re-check load could complete before that store was visible (x86 store buffer; arm64 lets an acquire load pass a release store). A drainer that cleared the re-checked pointer and then scanned could see neither the entry nor a changed pointer. Make the nested[] store and CallTraceStorage::put()'s re-check load SEQ_CST, and start each scan pass with a SEQ_CST fence for the drainer side. The root path already gets the ordering from the count++ RMW after its active_ptr store. rotate()'s retry of a buffer whose clear was skipped now makes a short series of non-waiting isReferenced() scans instead of a full drain, so a stuck straggler no longer costs a second ~500ms timeout per cycle on the dump thread. It shares the clear-or-remember step with clearStandby(). Say that rotate() relies on _state_lock now that it shares the skipped buffer record with clearStandby() and clearAll(), that isReferenced() is not a drain, which guard goes unrecorded on nesting overflow, and that reusing an uncleared buffer keeps the stale id by choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
What does this PR do?:
Fixes two gaps in the dictionary reclamation from #839, both found by Bits Code Review on #840.
Guard scan could miss a protected resource (use-after-free window). A nested guard used to install its resource in active_ptr and park the displaced outer resource in outer_stack, then move it back when it ended. The scanner reads active_ptr, then the stack, so it could read each location while the resource was in the other one:
Nested guards now record their own resource in nested[depth - 1] (formerly outer_stack) and never touch active_ptr, which keeps the root guard's resource. Every protected resource stays in one place from its guard's construction to its destruction, so one pass over active_ptr and nested[] cannot miss it.
Reused uncleared buffer could resurrect a stale id. When clearStandby() skips its target because a guard is still held, the next rotate() makes that buffer active again.
Motivation:
PROF-16136. Gap 1 is the same use-after-free class as the original ticket.
Gap 2 needs a straggler insert that stalls across a whole dump cycle to matter. The retry covers everything short of a thread stuck that long, and that remaining case is now reported.
Additional Notes:
rotate() now returns bool. It is not [[nodiscard]], so existing test and fuzz callers are unchanged.
How to test the change?:
For Datadog employees:
credentials of any kind, I've requested a security review (run the dd:platform-security-review
skill, or file a request via the PSEC review form).
bewaire also runs automatically on every PR.
🤖 Generated with Claude Code