pgstat: Bring up pgstat in BaseInit() to fix uninitialized use of pgstat by AV.

Previously pgstat_initialize() was called in InitPostgres() and
AuxiliaryProcessMain(). As it turns out there was at least one case where we
reported stats before pgstat_initialize() was called, see
AutoVacWorkerMain()'s intentionally early call to pgstat_report_autovac().

This turns out to not be a problem with the current pgstat implementation as
pgstat_initialize() only registers a shutdown callback. But in the shared
memory based stats implementation we are working towards pgstat_initialize()
has to do more work.

After b406478b87 BaseInit() is a central place where initialization shared by
normal backends and auxiliary backends can be put. Obviously BaseInit() is
called before InitPostgres() registers ShutdownPostgres. Previously
ShutdownPostgres was the first before_shmem_exit callback, now that's commonly
pgstats. That should be fine.

Previously pgstat_initialize() was not called in bootstrap mode, but there
does not appear to be a need for that. It's now done unconditionally.

To detect future issues like this, assertions are added to a few places
verifying that the pgstat subsystem is initialized and not yet shut down.

Author: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/20210405092914.mmxqe7j56lsjfsej@alap3.anarazel.de
Discussion: https://postgr.es/m/20210802164124.ufo5buo4apl6yuvs@alap3.anarazel.de
This commit is contained in:
Andres Freund 2021-08-06 18:54:39 -07:00
parent 5c056b0c25
commit ee3f8d3d3a
3 changed files with 65 additions and 13 deletions

View File

@ -125,8 +125,6 @@ AuxiliaryProcessMain(AuxProcType auxtype)
*/ */
CreateAuxProcessResourceOwner(); CreateAuxProcessResourceOwner();
/* Initialize statistics reporting */
pgstat_initialize();
/* Initialize backend status information */ /* Initialize backend status information */
pgstat_beinit(); pgstat_beinit();

View File

@ -295,6 +295,15 @@ static List *pending_write_requests = NIL;
*/ */
static instr_time total_func_time; static instr_time total_func_time;
/*
* For assertions that check pgstat is not used before initialization / after
* shutdown.
*/
#ifdef USE_ASSERT_CHECKING
static bool pgstat_is_initialized = false;
static bool pgstat_is_shutdown = false;
#endif
/* ---------- /* ----------
* Local function forward declarations * Local function forward declarations
@ -330,6 +339,7 @@ static void pgstat_send_connstats(bool disconnect, TimestampTz last_report);
static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared); static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
static void pgstat_setup_memcxt(void); static void pgstat_setup_memcxt(void);
static void pgstat_assert_is_up(void);
static void pgstat_setheader(PgStat_MsgHdr *hdr, StatMsgType mtype); static void pgstat_setheader(PgStat_MsgHdr *hdr, StatMsgType mtype);
static void pgstat_send(void *msg, int len); static void pgstat_send(void *msg, int len);
@ -854,6 +864,8 @@ pgstat_report_stat(bool disconnect)
TabStatusArray *tsa; TabStatusArray *tsa;
int i; int i;
pgstat_assert_is_up();
/* /*
* Don't expend a clock check if nothing to do. * Don't expend a clock check if nothing to do.
* *
@ -1960,6 +1972,8 @@ pgstat_init_function_usage(FunctionCallInfo fcinfo,
PgStat_BackendFunctionEntry * PgStat_BackendFunctionEntry *
find_funcstat_entry(Oid func_id) find_funcstat_entry(Oid func_id)
{ {
pgstat_assert_is_up();
if (pgStatFunctions == NULL) if (pgStatFunctions == NULL)
return NULL; return NULL;
@ -2078,6 +2092,8 @@ get_tabstat_entry(Oid rel_id, bool isshared)
TabStatusArray *tsa; TabStatusArray *tsa;
bool found; bool found;
pgstat_assert_is_up();
/* /*
* Create hash table if we don't have it already. * Create hash table if we don't have it already.
*/ */
@ -2938,6 +2954,8 @@ pgstat_fetch_replslot(NameData slotname)
static void static void
pgstat_shutdown_hook(int code, Datum arg) pgstat_shutdown_hook(int code, Datum arg)
{ {
Assert(!pgstat_is_shutdown);
/* /*
* If we got as far as discovering our own database ID, we can report what * If we got as far as discovering our own database ID, we can report what
* we did to the collector. Otherwise, we'd be sending an invalid * we did to the collector. Otherwise, we'd be sending an invalid
@ -2946,13 +2964,17 @@ pgstat_shutdown_hook(int code, Datum arg)
*/ */
if (OidIsValid(MyDatabaseId)) if (OidIsValid(MyDatabaseId))
pgstat_report_stat(true); pgstat_report_stat(true);
#ifdef USE_ASSERT_CHECKING
pgstat_is_shutdown = true;
#endif
} }
/* ---------- /* ----------
* pgstat_initialize() - * pgstat_initialize() -
* *
* Initialize pgstats state, and set up our on-proc-exit hook. * Initialize pgstats state, and set up our on-proc-exit hook. Called from
* Called from InitPostgres and AuxiliaryProcessMain. * BaseInit().
* *
* NOTE: MyDatabaseId isn't set yet; so the shutdown hook has to be careful. * NOTE: MyDatabaseId isn't set yet; so the shutdown hook has to be careful.
* ---------- * ----------
@ -2960,6 +2982,8 @@ pgstat_shutdown_hook(int code, Datum arg)
void void
pgstat_initialize(void) pgstat_initialize(void)
{ {
Assert(!pgstat_is_initialized);
/* /*
* Initialize prevWalUsage with pgWalUsage so that pgstat_send_wal() can * Initialize prevWalUsage with pgWalUsage so that pgstat_send_wal() can
* calculate how much pgWalUsage counters are increased by subtracting * calculate how much pgWalUsage counters are increased by subtracting
@ -2969,6 +2993,10 @@ pgstat_initialize(void)
/* Set up a process-exit hook to clean up */ /* Set up a process-exit hook to clean up */
on_shmem_exit(pgstat_shutdown_hook, 0); on_shmem_exit(pgstat_shutdown_hook, 0);
#ifdef USE_ASSERT_CHECKING
pgstat_is_initialized = true;
#endif
} }
/* ------------------------------------------------------------ /* ------------------------------------------------------------
@ -3001,6 +3029,8 @@ pgstat_send(void *msg, int len)
{ {
int rc; int rc;
pgstat_assert_is_up();
if (pgStatSock == PGINVALID_SOCKET) if (pgStatSock == PGINVALID_SOCKET)
return; return;
@ -3053,6 +3083,8 @@ pgstat_send_bgwriter(void)
/* We assume this initializes to zeroes */ /* We assume this initializes to zeroes */
static const PgStat_MsgBgWriter all_zeroes; static const PgStat_MsgBgWriter all_zeroes;
pgstat_assert_is_up();
/* /*
* This function can be called even if nothing at all has happened. In * This function can be called even if nothing at all has happened. In
* this case, avoid sending a completely empty message to the stats * this case, avoid sending a completely empty message to the stats
@ -4629,6 +4661,8 @@ backend_read_statsfile(void)
Oid inquiry_db; Oid inquiry_db;
int count; int count;
pgstat_assert_is_up();
/* already read it? */ /* already read it? */
if (pgStatDBHash) if (pgStatDBHash)
return; return;
@ -4765,6 +4799,17 @@ pgstat_setup_memcxt(void)
ALLOCSET_SMALL_SIZES); ALLOCSET_SMALL_SIZES);
} }
/*
* Stats should only be reported after pgstat_initialize() and before
* pgstat_shutdown(). This check is put in a few central places to catch
* violations of this rule more easily.
*/
static void
pgstat_assert_is_up(void)
{
Assert(pgstat_is_initialized && !pgstat_is_shutdown);
}
/* ---------- /* ----------
* pgstat_clear_snapshot() - * pgstat_clear_snapshot() -
@ -4779,6 +4824,8 @@ pgstat_setup_memcxt(void)
void void
pgstat_clear_snapshot(void) pgstat_clear_snapshot(void)
{ {
pgstat_assert_is_up();
/* Release memory, if any was allocated */ /* Release memory, if any was allocated */
if (pgStatLocalContext) if (pgStatLocalContext)
MemoryContextDelete(pgStatLocalContext); MemoryContextDelete(pgStatLocalContext);
@ -5897,6 +5944,8 @@ pgstat_slru_name(int slru_idx)
static inline PgStat_MsgSLRU * static inline PgStat_MsgSLRU *
slru_entry(int slru_idx) slru_entry(int slru_idx)
{ {
pgstat_assert_is_up();
/* /*
* The postmaster should never register any SLRU statistics counts; if it * The postmaster should never register any SLRU statistics counts; if it
* did, the counts would be duplicated into child processes via fork(). * did, the counts would be duplicated into child processes via fork().

View File

@ -517,6 +517,14 @@ BaseInit(void)
*/ */
DebugFileOpen(); DebugFileOpen();
/*
* Initialize statistics reporting. This needs to happen early to ensure
* that pgstat's shutdown callback runs after the shutdown callbacks of
* all subsystems that can produce stats (like e.g. transaction commits
* can).
*/
pgstat_initialize();
/* Do local initialization of file, storage and buffer managers */ /* Do local initialization of file, storage and buffer managers */
InitFileAccess(); InitFileAccess();
InitSync(); InitSync();
@ -646,10 +654,6 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
/* Initialize portal manager */ /* Initialize portal manager */
EnablePortalManager(); EnablePortalManager();
/* Initialize stats collection --- must happen before first xact */
if (!bootstrap)
pgstat_initialize();
/* Initialize status reporting */ /* Initialize status reporting */
if (!bootstrap) if (!bootstrap)
pgstat_beinit(); pgstat_beinit();
@ -662,11 +666,12 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
/* /*
* Set up process-exit callback to do pre-shutdown cleanup. This is the * Set up process-exit callback to do pre-shutdown cleanup. This is the
* first before_shmem_exit callback we register; thus, this will be the * one of the first before_shmem_exit callbacks we register; thus, this
* last thing we do before low-level modules like the buffer manager begin * will be one the last things we do before low-level modules like the
* to close down. We need to have this in place before we begin our first * buffer manager begin to close down. We need to have this in place
* transaction --- if we fail during the initialization transaction, as is * before we begin our first transaction --- if we fail during the
* entirely possible, we need the AbortTransaction call to clean up. * initialization transaction, as is entirely possible, we need the
* AbortTransaction call to clean up.
*/ */
before_shmem_exit(ShutdownPostgres, 0); before_shmem_exit(ShutdownPostgres, 0);