BUG #19755: Server-wide hang: DROP DATABASE waits forever for ProcSignalBarrier while VACUUM waits on pg_databas

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: anncalla(at)163(dot)com
Subject: BUG #19755: Server-wide hang: DROP DATABASE waits forever for ProcSignalBarrier while VACUUM waits on pg_databas
Date: 2026-10-12 12:25:57
Message-ID: 19755-6ffb2ecb4e3fd782@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19755
Logged by: Ann Calla
Email address: anncalla(at)163(dot)com
PostgreSQL version: 18.6
Operating system: Ubuntu 24.04 x86_64
Description:

Description
-----------

With several sessions concurrently running DROP DATABASE / CREATE DATABASE
and database-wide VACUUM FULL in other databases, the server can enter a
state that it never leaves:

* DROP DATABASE logs, every 5 seconds and indefinitely,
LOG: still waiting for backend with PID 1770 to accept
ProcSignalBarrier
STATEMENT: DROP DATABASE IF EXISTS sN
* the backend it names is a VACUUM that is waiting for a heavyweight lock
("VACUUM waiting" in the process title);
* every other VACUUM / ANALYZE / CREATE DATABASE, the autovacuum launcher
and all new connections ("startup waiting") queue up behind it, so the
server accepts no new sessions;
* no "deadlock detected" error is raised;
* the named VACUUM backend does not react to SIGTERM
(pg_terminate_backend equivalent) or to a manual SIGUSR1.

Only a server restart recovers. We first hit this with an automated test
workload (8 client sessions, one database per session); the server stayed
in this state for more than 24 hours until it was restarted.

How to reproduce
----------------

On a freshly initialised cluster, run the attached repro.sh inside the
server container (it only needs psql and a trusted local superuser):

docker run -d --name pg -e POSTGRES_HOST_AUTH_METHOD=trust
postgres:latest
docker cp repro.sh pg:/repro.sh
docker exec -u postgres pg bash /repro.sh 300
docker logs pg 2>&1 | grep ProcSignalBarrier

The script starts 12 sessions. Each one loops over:

DROP DATABASE IF EXISTS sN; CREATE DATABASE sN; -- connected to
"test"
-- then, connected to sN: a few CREATE TABLE / INSERT statements,
CREATE TEMP VIEW ...; ANALYZE; TRUNCATE t3; DISCARD TEMP;
VACUUM FULL;
VACUUM;

On a fresh cluster the hang appeared within two minutes in 2 of 2 runs.
It is timing dependent: on a cluster that had already been through a
restart, two runs of 4 and 9 minutes did not hang. The same workload with
"VACUUM FULL t0; VACUUM FULL t1; ..." (table-level) instead of the
database-wide "VACUUM FULL" ran for 6 minutes without hanging.

Stack traces
------------

Taken with gdb from the hung server (no debug symbols; full output of all
backends is attached as stacks.txt).

The DROP DATABASE backend:

epoll_wait <- WaitEventSetWait <- WaitLatch <-
ConditionVariableTimedSleep
<- WaitForProcSignalBarrier <- dropdb <- standard_ProcessUtility

The backend it is waiting for (PID 1770), and five other VACUUM backends:

epoll_wait <- WaitEventSetWait <- WaitLatch <- ProcSleep
<- LockAcquireExtended <- LockRelationOid <- relation_open <- table_open
<- vac_update_datfrozenxid <- vacuum <- ExecVacuum

Four further VACUUM backends:

... ProcSleep <- LockAcquireExtended <- LockRelationOid
<- try_relation_open <- vacuum_open_relation <- vacuum <- ExecVacuum

CREATE DATABASE:

... ProcSleep <- LockAcquireExtended <- LockRelationOid <- relation_open
<- table_open <- createdb

Analysis (from reading the source; not confirmed with a debug build)
---------------------------------------------------------------------

vac_update_datfrozenxid() calls vac_truncate_clog(), which is static and
appears to be inlined in this build. vac_truncate_clog() does

LWLockAcquire(WrapLimitsVacuumLock, LW_EXCLUSIVE);
...
relation = table_open(DatabaseRelationId, AccessShareLock);

so the heavyweight lock on pg_database is requested while an LWLock is
held, i.e. with interrupts held off. If that request has to wait, the
backend can neither absorb a ProcSignalBarrier nor be cancelled or
terminated. That would give this cycle, which the deadlock detector cannot
see because one edge is not a heavyweight lock:

A: DROP DATABASE holds RowExclusiveLock on pg_database,
waits in WaitForProcSignalBarrier() for B
B: VACUUM holds WrapLimitsVacuumLock (interrupts held off),
waits for AccessShareLock on pg_database behind C
C: VACUUM FULL (database-wide, now processing pg_database)
waits for AccessExclusiveLock on pg_database,
blocked by A

This matches the observations that the named backend ignores SIGTERM and
that the hang needs a database-wide VACUUM FULL, the only statement in the
workload that requests AccessExclusiveLock on pg_database.

Expected behaviour
------------------

DROP DATABASE should complete, or one of the participants should fail with
an error. A lock wait on pg_database should not be uninterruptible.

Browse pgsql-bugs by date

  From Date Subject
Next Message Andrey Rachitskiy 2026-10-12 15:18:52 Re: BUG #19732: first_value/last_value/nth_value return NULL with EXCLUDE TIES when the current row is outside its f
Previous Message Matthias van de Meent 2026-10-12 10:57:37 Re: BUG #19519: REPACK can fail due to missing chunk for toast value