diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c index 09cd9722517..b3a16033378 100644 --- a/src/backend/access/transam/parallel.c +++ b/src/backend/access/transam/parallel.c @@ -946,9 +946,14 @@ WaitForParallelWorkersToExit(ParallelContext *pcxt) * up safely -- we won't be able to tell when our workers are actually * dead. This doesn't necessitate a PANIC since they will all abort * eventually, but we can't safely continue this session. + * + * Don't raise the FATAL (which would re-enter proc_exit()) if this is + * called while the backend is already exiting; there's no session + * left to end. This can happen when AbortOutOfAnyTransaction() runs + * from the ShutdownPostgres exit callback. */ if (status == BGWH_POSTMASTER_DIED) - ereport(FATAL, + ereport(proc_exit_inprogress ? LOG : FATAL, (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("postmaster exited during a parallel transaction"))); diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index bc3c4334aeb..7fb424a743e 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -4021,8 +4021,14 @@ stop_repack_decoding_worker(void) status = WaitForBackgroundWorkerShutdown(decoding_worker->handle); RESUME_INTERRUPTS(); + /* + * Don't raise the FATAL (which would re-enter proc_exit()) if this + * is called while the backend is already exiting; there's no session + * left to end. This can happen when the before_shmem_exit callback + * runs during the nested proc_exit() after another FATAL error. + */ if (status == BGWH_POSTMASTER_DIED) - ereport(FATAL, + ereport(proc_exit_inprogress ? LOG : FATAL, errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("postmaster exited during REPACK command")); } diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index 5926c0c8b9c..78dd924f390 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -635,19 +635,38 @@ pgstat_shutdown_hook(int code, Datum arg) Assert(IsUnderPostmaster || !IsPostmasterEnvironment); /* - * If we got as far as discovering our own database ID, we can flush out - * what we did so far. Otherwise, we'd be reporting an invalid database - * ID, so forget it. (This means that accesses to pg_database during - * failed backend starts might never get counted.) + * Normally the transaction state has been cleaned up before we get + * here, by AbortOutOfAnyTransaction() in the ShutdownPostgres exit + * callback. But if a FATAL error is raised during that cleanup -- + * either from a parallel context waiting for our workers, or the + * exit-on-postmaster-death handling in WaitEventSetWait() -- + * errfinish() re-enters proc_exit() and runs the remaining exit + * callbacks, this one included, with the transaction state not + * cleared. pgstat_report_stat() must not be called in that state, + * so drop whatever is pending instead. */ - if (OidIsValid(MyDatabaseId)) - pgstat_report_disconnect(MyDatabaseId); + if (IsTransactionOrTransactionBlock()) + { + dlist_init(&pgStatPending); + } + else + { + /* + * If we got as far as discovering our own database ID, we can + * flush out what we did so far. Otherwise, we'd be reporting + * an invalid database ID, so forget it. (This means that + * accesses to pg_database during failed backend starts might + * never get counted.) + */ + if (OidIsValid(MyDatabaseId)) + pgstat_report_disconnect(MyDatabaseId); - pgstat_report_stat(true); + pgstat_report_stat(true); - /* there shouldn't be any pending changes left */ - Assert(dlist_is_empty(&pgStatPending)); - dlist_init(&pgStatPending); + /* there shouldn't be any pending changes left */ + Assert(dlist_is_empty(&pgStatPending)); + dlist_init(&pgStatPending); + } /* drop the backend stats entry */ if (!pgstat_drop_entry(PGSTAT_KIND_BACKEND, InvalidOid, MyProcNumber, false))