Skip to content

sqllogictest: account before alloc to avoid panic-after-alloc hazards#22742

Merged
avantgardnerio merged 1 commit into
apache:mainfrom
coralogix:brent/rowconverter-append-untracked-alloc
Jun 3, 2026
Merged

sqllogictest: account before alloc to avoid panic-after-alloc hazards#22742
avantgardnerio merged 1 commit into
apache:mainfrom
coralogix:brent/rowconverter-append-untracked-alloc

Conversation

@avantgardnerio
Copy link
Copy Markdown
Contributor

Which issue does this PR close?

  • Closes #.

Follow-up defect fix in the allocator-level accounting framework added by #22626.

Rationale for this change

AccountingAllocator::realloc (introduced in #22626) called inner.realloc first — which frees the caller's old pointer on success — then called track, which can panic_any on overdraft. The unwind starts with the caller's Vec (or similar growing container) still holding the now-freed old pointer; the next Drop produces glibc's double free or corruption (out) and SIGABRT, masking the underlying untracked-allocation panic the framework was trying to surface.

The same hazard does not apply to alloc / alloc_zeroed / dealloc:

  • alloc / alloc_zeroed — the caller has no preexisting pointer to be invalidated; panicking after the inner alloc just leaks the new allocation, which is fine on the kill path.
  • dealloc — credits the bank, never panics.

Only realloc has a live caller-side pointer that gets invalidated by the inner operation before the panic decision.

What changes are included in this PR?

Reorder AccountingAllocator::realloc to account first, forward to inner.realloc second:

  • track(delta) first — on overdraft we panic with the caller's pointer still valid; unwind drops the live container cleanly, no abort.
  • If inner.realloc returns null after a successful track, refund the delta so the bank stays consistent with what actually got allocated.

Are these changes tested?

Manually verified using a GroupedHashAggregateStream + List<Utf8> group-key reproducer (routes through GroupValuesRows::internRowConverter::appendVec::resize__rust_realloc, which is one of the untracked-allocation sites this framework is meant to catch):

  • before: exit 101, double free or corruption (out), signal 6 SIGABRT
  • after: exit 1, clean allocator overdraft: account balance at panic = -1.7 MB at the same RowConverter::append stack frame

A regression test for "panic from inside realloc does not double-free" would require constructing the precise realloc-mid-grow scenario plus a catch_unwind harness; deferred as follow-up.

Are there any user-facing changes?

No. Confined to sqllogictest's memory-accounting feature, which is internal CI tooling.

@github-actions github-actions Bot added the sqllogictest SQL Logic Tests (.slt) label Jun 3, 2026
@avantgardnerio avantgardnerio requested a review from andygrove June 3, 2026 19:29
`AccountingAllocator` forwarded every op to `inner` first and called
`track` after. `track` can `panic_any` on overdraft, and the
"inner first / account after" order produces two distinct hazards:

1. **`realloc`** — `inner.realloc` frees the caller's old pointer on
   success. If `track` then panics, unwind starts with the caller's
   `Vec` (or similar growing container) still holding the freed old
   pointer; the next `Drop` produces glibc's `double free or
   corruption (out)` and SIGABRT, masking the underlying
   untracked-allocation panic the framework was trying to surface.

2. **`alloc` / `alloc_zeroed`** — `inner.alloc` succeeds and bytes
   are physically allocated. If `track` then panics, no caller ever
   receives the pointer → unwind leaks the very bytes that pushed
   the bank over budget — the opposite of what the kill panic is for.

`dealloc` is safe: it's a credit only, and `track` short-circuits
on `delta >= 0` before any panic decision.

Reorder all three ops: `track(delta)` first, forward to `inner` second.
On overdraft we panic before touching `inner`, so the caller's
pointer (`alloc`/`alloc_zeroed`: never returned, never owned;
`realloc`: still valid old pointer) is in a consistent state for
unwind. If `inner` then returns null we refund the delta so the bank
stays consistent.

Manually verified with a `GroupedHashAggregateStream` + `List<Utf8>`
group-key reproducer that routes through `GroupValuesRows::intern` →
`RowConverter::append` → `Vec::resize` → `__rust_realloc`:

- before: exit 101, `double free or corruption (out)`, signal 6 SIGABRT
- after:  exit 1, clean `allocator overdraft` panic at the same
  `RowConverter::append` stack frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avantgardnerio avantgardnerio force-pushed the brent/rowconverter-append-untracked-alloc branch from 624b8cd to d8cc8c3 Compare June 3, 2026 19:34
@avantgardnerio avantgardnerio changed the title sqllogictest: account before realloc to avoid panic-after-free sqllogictest: account before alloc to avoid panic-after-alloc hazards Jun 3, 2026
Copy link
Copy Markdown
Member

@andygrove andygrove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks @avantgardnerio

@avantgardnerio avantgardnerio added this pull request to the merge queue Jun 3, 2026
Merged via the queue into apache:main with commit cf01af5 Jun 3, 2026
36 checks passed
@avantgardnerio avantgardnerio deleted the brent/rowconverter-append-untracked-alloc branch June 3, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants