From c817fc9373ca08488088414a052eaf77dbb55bde Mon Sep 17 00:00:00 2001
From: Karina Litskevich <litskevichkarina@gmail.com>
Date: Tue, 5 Nov 2024 20:27:25 +0300
Subject: [PATCH v1] pg_stat_statements: Avoid excessive lock holding

Entry's mutex spinlock is used to be able to modify counters without
holding pgss->lock exclusively (though holding it at least shared so
the entry doesn't disappear). We never actually modify stats_since and
minmax_stats_since without holding pgss->lock exclusively, so there is
no need to hold entry's mutex spinlock when reading stats_since and
minmax_stats_since.

This also restores the consistency between the code and the comments
about entry's mutex spinlock usage.
---
 contrib/pg_stat_statements/pg_stat_statements.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 1798e1d016..a66ad99a37 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1869,9 +1869,16 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 		/* copy counters to a local variable to keep locking time short */
 		SpinLockAcquire(&entry->mutex);
 		tmp = entry->counters;
+		SpinLockRelease(&entry->mutex);
+
+		/*
+		 * There is no need to hold entry->mutex when reading stats_since and
+		 * minmax_stats_since for (unlike counters) they are always written
+		 * while holding pgss->lock exclusively. We are holding pgss->lock
+		 * shared so there should be no race here.
+		 */
 		stats_since = entry->stats_since;
 		minmax_stats_since = entry->minmax_stats_since;
-		SpinLockRelease(&entry->mutex);
 
 		/* Skip entry if unexecuted (ie, it's a pending "sticky" entry) */
 		if (IS_STICKY(tmp))
-- 
2.34.1

