| From: | Rafia Sabih <rafia(dot)pghackers(at)gmail(dot)com> |
|---|---|
| To: | jacob(at)brejnbjerg(dot)com, pgsql-bugs(at)lists(dot)postgresql(dot)org, Robert Haas <robertmhaas(at)gmail(dot)com>, Andres Freund <andres(at)anarazel(dot)de> |
| Subject: | Re: BUG #19557: Parallel leader stuckat ParallelFinish leaked InterruptHoldoffCount |
| Date: | 2026-07-24 12:06:59 |
| Message-ID: | CA+FpmFdSiGvRZQMGJTuv+vrPpLVYj=S4_=4XtpggtTcROBWuUg@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
Adding Robert and Andres because they know these areas best and can provide
some valuable insights to this issue.
On Fri, 17 Jul 2026 at 22:00, PG Bug reporting form <noreply(at)postgresql(dot)org>
wrote:
> The following bug has been logged on the website:
>
> Bug reference: 19557
> Logged by: Jacob Mørk Brejnbjerg
> Email address: jacob(at)brejnbjerg(dot)com
> PostgreSQL version: 18.4
> Operating system: Ubuntu 24.04 (kernel 6.8.0-124-generic)
> Description:
>
> A backend executing a parallel query has been stuck for 40+ hours in
> wait_event IPC/ParallelFinish. All of its parallel workers exited cleanly
> (exit code 0, nothing logged). pg_cancel_backend() and
> pg_terminate_backend() have no effect. We kept the backend alive and
> debugged it with gdb + debug symbols; the evidence shows a leaked
> InterruptHoldoffCount, plus a design interaction that turns any such leak
> into a permanently unkillable backend.
>
> Possibly relevant configuration: io_method = io_uring,
> max_parallel_workers_per_gather = 48,
> shared_preload_libraries = 'timescaledb,pg_parquet'
> (timescaledb 2.27.2, pg_parquet 0.5.1).
> PostgreSQL 18.4 (Ubuntu 18.4-1.pgdg24.04+1).
>
> The query is a COPY (SELECT <aggregation over one table> GROUP BY ...)
> TO STDOUT (FORMAT parquet) issued via pg_parquet's COPY hook; the inner
> select runs a parallel plan (Gather, 10+ workers planned) over
> TimescaleDB compressed-hypertable chunks, i.e. an AIO-read-heavy scan.
>
> Backtrace of the stuck leader (main thread; the only other thread is an
> io_uring kernel worker, "iou-wrk-<pid>"):
>
> #0 epoll_wait (epfd=754, events=..., maxevents=1, timeout=-1)
> #1 WaitEventSetWait ()
> #2 WaitLatch ()
> #3 WaitForParallelWorkersToFinish ()
> #4 ExecParallelFinish ()
> #5-#8 (Gather shutdown path, static functions)
> #9 standard_ExecutorRun ()
> #10-#11 PortalRun ()
> #12-#17 pg_parquet.so (COPY TO hook driving the portal)
> #18 prev_ProcessUtility (timescaledb process_utility.c:124)
> #19 timescaledb_ddl_command_start (...)
> #20-#22 PortalRun ()
> #23-#24 PostgresMain ()
>
> Key state read from the live process via gdb:
>
> InterruptPending = 1
> ProcDiePending = 1 <- pg_terminate_backend arrived, never
> serviced
> QueryCancelPending = 1 <- pg_cancel_backend arrived, never serviced
> InterruptHoldoffCount = 1 <- held, forever
> CritSectionCount = 0
> num_held_lwlocks = 0 <- NO LWLock is held
> ParallelMessagePending = 1 <- worker messages queued, unprocessable
>
> /proc signal state: SigPnd/ShdPnd are all zero - the SIGTERM/SIGINT were
> delivered and consumed by the handlers (which set the flags above); the
> backend wakes from WaitLatch, but CHECK_FOR_INTERRUPTS() is a no-op with
> InterruptHoldoffCount > 0, so it just re-sleeps.
>
> Why this is a permanent hang and not just an uncancellable wait:
> worker-exit accounting is itself interrupt-driven. The workers' detach
> notifications are only processed via CHECK_FOR_INTERRUPTS() ->
> ProcessParallelMessages(), which is what clears each worker's error_mqh.
> With the holdoff leaked, ProcessParallelMessages never runs
> (ParallelMessagePending stays 1 - verified in the live process), so
> WaitForParallelWorkersToFinish() believes the workers are still attached
> and waits forever. The same leaked holdoff blocks ProcDiePending. One
> leaked holdoff therefore produces both symptoms; only SIGKILL
> (crash-restart) or a debugger can clear the backend.
>
> On the origin of the leak: num_held_lwlocks = 0 rules out a stuck
> LWLockAcquire, and the server log shows NO error from this backend for
> its entire lifetime, which we believe rules out the
> "ereport(ERROR) raised under HOLD, caught by PG_TRY" pattern (the raise
> would have been logged by errfinish). That points to a straight-line
> code path that called HOLD_INTERRUPTS() and returned without
> RESUME_INTERRUPTS(). We audited the read path: bufmgr.c, read_stream.c,
> shm_mq.c, condition_variable.c contain no bare holds; timescaledb 2.27.2
> and pg_parquet 0.5.1 (and pgrx 0.16.1) contain no HOLD_INTERRUPTS at
> all. The remaining bare holds on this workload's path are in
> src/backend/storage/aio/aio.c (four HOLD/RESUME regions, including
> pgaio_io_reclaim which runs completion callbacks under HOLD) and the
> parallel-message path itself. Given io_method = io_uring and an
> AIO-heavy scan we suspect an AIO path, but we could not identify the
> exact site from the live process.
>
> Frequency: once in ~6 days of heavy production use of this COPY path
> (thousands of executions/day). Not deterministically reproducible so
> far.
>
> We have a gcore of the stuck backend and can keep it (and/or the live
> process) available - happy to run additional gdb commands against it.
>
> Separately, it may be worth considering a defensive measure: because
> WaitForParallelWorkersToFinish depends on interrupt processing for its
> own termination condition, any holdoff leak from any subsystem converts
> into a silent, unkillable backend. Detecting "waiting on ParallelFinish
> with INTERRUPTS_CAN_BE_PROCESSED() false and ParallelMessagePending set"
> and at least logging (or treating it as an invariant violation) would
> make this failure mode visible instead of silent.
>
>
>
>
>
--
Regards,
Rafia Sabih
CYBERTEC PostgreSQL International GmbH
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Nitin Motiani | 2026-07-24 16:50:12 | Re: BUG #19560: Wrong results since v16: removable LEFT JOIN silently drops WHERE qual |
| Previous Message | Zhijie Hou (Fujitsu) | 2026-07-24 11:39:25 | RE: BUG #19519: REPACK can fail due to missing chunk for toast value |