SIGSEGV in dynahash

Started by Konstantin Knizhnik8 days ago4 messagesbugs
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253421
psql -h localhost -U postgres

Built from patchset v1 (message #1), August 23, 2026 at 01:43 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253421_1 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253421_1 && git checkout t253421_1

Patchset v1 (message #1) is on t253421_1

Jump to latest
#1Konstantin Knizhnik
k.knizhnik@postgrespro.ru

On PG19,|ShmemInitHash|always builds afixed-sizeshared hash with abump
allocator(|ShmemHashAlloc|) whose|alloc_arg|is astack-localregion used
only during|hash_create|. After init, that pointer is dead.

In|hash_search|, for every|HASH_ENTER|/|HASH_ENTER_NULL|, dynahash does
thisbeforelookup:

dynahash.cLines927-937
if(action ==HASH_ENTER ||action ==HASH_ENTER_NULL)
{
if(hctl->freeList[0].nentries>(int64)hctl->max_bucket&&
!IS_PARTITIONED(hctl)&&!hashp->frozen&&
!has_seq_scans(hashp))
(void)expand_table(hashp);
}

It may cause SIGSEGV in case of using HASH_ENTER_NULL:

hash_search(HASH_ENTER_NULL)
→ expand_table → seg_alloc → SIGSEGV in libc (MemSet/alloc)

Pre-PG19, shared hashes used|ShmemAllocNoError|from the global pool, so
a failed grow tended to return|NULL|/ error instead of faulting on a
dead bump allocator.

It was introduced by commit 9fe9ecd516b — Allocate all parts of shmem
hash table from a single contiguous area

Patch preventing extension of fixed dynahash is attached.

Attachments:

t253421_1
0001-prevent-fixed-dynahash-extension-20260815.patchtext/plain; charset=UTF-8; name=0001-prevent-fixed-dynahash-extension-20260815.patchDownload+3-1
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Konstantin Knizhnik (#1)
Re: SIGSEGV in dynahash

Konstantin Knizhnik <knizhnik@garret.ru> writes:

On PG19,|ShmemInitHash|always builds afixed-sizeshared hash with abump
allocator(|ShmemHashAlloc|) whose|alloc_arg|is astack-localregion used
only during|hash_create|. After init, that pointer is dead.
...
Pre-PG19, shared hashes used|ShmemAllocNoError|from the global pool, so
a failed grow tended to return|NULL|/ error instead of faulting on a
dead bump allocator.
It was introduced by commit 9fe9ecd516b — Allocate all parts of shmem
hash table from a single contiguous area

Yeah. I'm not too pleased with 9fe9ecd516b for a different reason.

In pursuit of what seems to be a merely cosmetic goal (ie make shared
hashes be reported differently in pg_shmem_allocations), it's made a
fundamental and IMO possibly destabilizing change in the behavior of
shared-memory hash tables. To wit, it is no longer possible to expand
a shared hash table beyond its startup-time allocation. For some of
them that doesn't matter, but for others it definitely does; the lock
table in particular is sized only heuristically. For the last couple
of decades, there was slop in the max_locks_per_transaction limit
because the lock table could grow into the 100kB slop space we leave
in shared memory; but now there is no slop. I suspect we will get
complaints from people whose workloads used to work without trouble
and now don't. There might be extension code that depends on shared
hashtables not having a hard limit, too.

I wonder whether we shouldn't just revert this.

regards, tom lane

#3Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#2)
Re: SIGSEGV in dynahash

On Sat, Aug 15, 2026 at 12:12:29PM -0400, Tom Lane wrote:

Yeah. I'm not too pleased with 9fe9ecd516b for a different reason.

In pursuit of what seems to be a merely cosmetic goal (ie make shared
hashes be reported differently in pg_shmem_allocations), it's made a
fundamental and IMO possibly destabilizing change in the behavior of
shared-memory hash tables. To wit, it is no longer possible to expand
a shared hash table beyond its startup-time allocation.

There is a difference between shmem_hash.c and dynahash.c. dynahash.c
is able to support a growing size, but it's not the case of
shmem_hash.c.

Note also some comments in dynahash.c:
* table grows much beyond the initial size. (Currently, shared memory hash
* tables are only created by ShmemRequestHash()/ShmemInitHash() though, which
* doesn't support growing at all.)

shmem_hash_create() also claims the non-growth argument as true as we
would need to grow the underlying shmem region linked with a hash
table defined by shmem_hash.c.

Or am I missing something?
--
Michael

#4Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#2)
Re: SIGSEGV in dynahash

On 15/08/2026 19:12, Tom Lane wrote:

Yeah. I'm not too pleased with 9fe9ecd516b for a different reason.

In pursuit of what seems to be a merely cosmetic goal (ie make shared
hashes be reported differently in pg_shmem_allocations), it's made a
fundamental and IMO possibly destabilizing change in the behavior of
shared-memory hash tables. To wit, it is no longer possible to expand
a shared hash table beyond its startup-time allocation. For some of
them that doesn't matter, but for others it definitely does; the lock
table in particular is sized only heuristically. For the last couple
of decades, there was slop in the max_locks_per_transaction limit
because the lock table could grow into the 100kB slop space we leave
in shared memory; but now there is no slop. I suspect we will get
complaints from people whose workloads used to work without trouble
and now don't. There might be extension code that depends on shared
hashtables not having a hard limit, too.

I wonder whether we shouldn't just revert this.

This was discussed at the time. My opinion, and I thought there was a
consensus on it, is that the unpredictable behavior of the "slop" was
not a good idea. The slop got assigned to whatever hash table happened
to use it first, and from there on it was reserved for that hash table
until server restart. That's pretty unpredictable, and hard to reason
about for tuning purposes.

It was all about the lock manager, none of the other hash tables used
the slop. The lock table consists of two hash tables, the "LOCK hash"
and the "PROCLOCK hash". The slop would get assigned to one of those
depending on the kind of queries you run after server startup. If you
ran a query that happened to need a lot of "LOCK hash" space, and then
ran a workload that needed more "PROCLOCK hash" space, the latter would
fail. But if you ran the queries in different order, then the slop was
assigned to the "PROLOCK hash" and it would work, but running the other
kind of query would fail instead. Weird.

I increased the default for max_locks_per_transaction (commit
79534f9065) to compensate for this, so that most applications that were
using the default settings and were relying on the slop will continue to
work.

- Heikki