* further if a longer lived reference is needed.
*/
pg_atomic_init_u32(&shhashent->refcount, 1);
+
+ /*
+ * Initialize "generation" to 0, as freshly created.
+ */
+ pg_atomic_init_u32(&shhashent->generation, 0);
shhashent->dropped = false;
chunk = dsa_allocate0(pgStatLocal.dsa, pgstat_get_kind_info(kind)->shared_size);
/* mark as not dropped anymore */
pg_atomic_fetch_add_u32(&shhashent->refcount, 1);
+
+ /*
+ * Increment "generation", to let any backend with local references know
+ * that what they point to is outdated.
+ */
+ pg_atomic_fetch_add_u32(&shhashent->generation, 1);
shhashent->dropped = false;
/* reinitialize content */
entry_ref->shared_stats = shheader;
entry_ref->shared_entry = shhashent;
+ entry_ref->generation = pg_atomic_read_u32(&shhashent->generation);
}
/*
* case are replication slot stats, where a new slot can be
* created with the same index just after dropping. But oid
* wraparound can lead to other cases as well. We just reset the
- * stats to their plain state.
+ * stats to their plain state, while incrementing its "generation"
+ * in the shared entry for any remaining local references.
*/
shheader = pgstat_reinit_entry(kind, shhashent);
pgstat_acquire_entry_ref(entry_ref, shhashent, shheader);
if (!shent)
elog(ERROR, "could not find just referenced shared stats entry");
- Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) == 0);
- Assert(entry_ref->shared_entry == shent);
-
- pgstat_free_entry(shent, NULL);
+ /*
+ * This entry may have been reinitialized while trying to release
+ * it, so double-check that it has not been reused while holding a
+ * lock on its shared entry.
+ */
+ if (pg_atomic_read_u32(&entry_ref->shared_entry->generation) ==
+ entry_ref->generation)
+ {
+ /* Same "generation", so we're OK with the removal */
+ Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) == 0);
+ Assert(entry_ref->shared_entry == shent);
+ pgstat_free_entry(shent, NULL);
+ }
+ else
+ {
+ /*
+ * Shared stats entry has been reinitialized, so do not drop
+ * its shared entry, only release its lock.
+ */
+ dshash_release_lock(pgStatLocal.shared_hash, shent);
+ }
}
}
*/
pg_atomic_uint32 refcount;
+ /*
+ * Counter tracking the number of times the entry has been reused.
+ *
+ * Set to 0 when the entry is created, and incremented by one each time
+ * the shared entry is reinitialized with pgstat_reinit_entry().
+ *
+ * May only be incremented / decremented while holding at least a shared
+ * lock on the dshash partition containing the entry. Like refcount, it
+ * needs to be an atomic variable because multiple backends can increment
+ * the generation with just a shared lock.
+ */
+ pg_atomic_uint32 generation;
+
/*
* Pointer to shared stats. The stats entry always starts with
* PgStatShared_Common, embedded in a larger struct containing the
*/
PgStatShared_Common *shared_stats;
+ /*
+ * Copy of PgStatShared_HashEntry->generation, keeping locally track of
+ * the shared stats entry "generation" retrieved (number of times reused).
+ */
+ uint32 generation;
+
/*
* Pending statistics data that will need to be flushed to shared memory
* stats eventually. Each stats kind utilizing pending data defines what