Re: Row pattern recognition

From: Henson Choi <assam258(at)gmail(dot)com>
To: Tatsuo Ishii <ishii(at)postgresql(dot)org>, jian(dot)universality(at)gmail(dot)com
Cc: zsolt(dot)parragi(at)percona(dot)com, sjjang112233(at)gmail(dot)com, vik(at)postgresfriends(dot)org, er(at)xs4all(dot)nl, jacob(dot)champion(at)enterprisedb(dot)com, david(dot)g(dot)johnston(at)gmail(dot)com, peter(at)eisentraut(dot)org, li(dot)evan(dot)chao(at)gmail(dot)com, pgsql-hackers(at)postgresql(dot)org, 신성준 <shinsj4653(at)gmail(dot)com>
Subject: Re: Row pattern recognition
Date: 2026-09-04 04:42:15
Message-ID: CAAAe_zAHbPO_jxVzZ5b=iyussKqi_wzJp1C-ubWA1LT7n++BZg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Tatsuo, Jian,

I would like to disagree with one part of this, late and after it has
been agreed to. Sorry for coming back to it only now.

>> I also intend to add below at the beginning of nfa_state_free
>> ``````
>> /* state at the free-list head was freed by the previous call */
>> if (unlikely(winstate->nfaStateFree == state))
>> elog(ERROR, "double free of RPR NFA state");
>> ``````
>
> Looks good to me.

I do not think this guard catches what its name claims. It detects
one thing only: the same pointer freed twice in a row, with no other
free in between. The comment says so itself -- it compares against
the state the previous call pushed. One unrelated free between the
two and it passes:

nfa_state_free(A) head = A
nfa_state_free(B) head = B, B->next = A
nfa_state_free(A) head is B, not A -- the check passes
A->next = B, head = A

The free list is now A -> B -> A and nothing reported it. Two
consecutive frees of one pointer is also the shape a reader is most
likely to catch in review, so the guard covers the case we would find
anyway and lets through the case that survives review.

> Ok. You are not confident the errors never happen. If so, using
> elog(ERROR) makes sense.

There were paths that failed to free, so the doubt is not an abstract
one. That was in February and March, and they were fixed. A great
deal has changed since, and I agree the memory handling is worth
verifying again -- I would rather that verification be done under
Valgrind than by a check compiled into release builds.

> On the hand if we are confident that the error is truley unlikely
> happen, using elog(ERROR) is just a waste of CPU.

Agreed, and it is worth saying where this one would land.
nfa_state_free() is called from eight sites, five of them inside the
per-row loops of nfa_match() and the advance paths, so whatever we put
in it is paid per state, per row, per partition.

And a double free is in fact already detected here, by a check that
does not have the limitation above.

In an ordinary build nothing is freed at all: nfa_state_free() pushes
the pointer onto a private list and the chunk stays live in
partcontext, so there is nothing for anything to observe. That is why
the function already carries:

#ifdef USE_VALGRIND
/* real free so Valgrind catches use-after-free instead of
recycling */
pfree(state);
#else
state->next = winstate->nfaStateFree;
winstate->nfaStateFree = state;
#endif

Under USE_VALGRIND that branch makes every free a real pfree(), and
since nothing ever assigns nfaStateFree there it stays NULL, so every
nfa_state_make() allocates afresh from MemoryContextAlloc(). Every
state therefore travels the ordinary palloc/pfree path.

USE_VALGRIND also defines MEMORY_CONTEXT_CHECKING on its own
(pg_config_manual.h), which turns on AllocSetFree()'s own guard:

/* Test for previously-freed chunk */
if (unlikely(chunk->requested_size == InvalidAllocSize))
elog(ERROR, "detected double pfree in %s %p",
set->header.name, chunk);

That is the check being proposed, already in the tree and without the
consecutive-free limitation: it tests a mark on the chunk itself
rather than the position of a list head, so it fires on any second
free. The comment above it names the same hazard -- "the result would
be a corrupted freelist that allows this chunk to get re-allocated
twice." Valgrind then adds the other half, since the freed chunk is
made NOACCESS: a state recycled while a caller still held it is
reported as an invalid read or write.

To be definite about it: this class of problem has to be verified by
other means than the one proposed -- in a Valgrind run, where the
detection already exists and is general, rather than by a branch
compiled into release builds.

Best regards,
Henson

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Paul A Jungwirth 2026-09-04 04:43:39 EXPLAIN ... FOR PORTION OF should not evaluate bounds
Previous Message Ewan Young 2026-09-04 04:39:59 Re: FOR PORTION OF silently ignored on views with DO INSTEAD rules