From 63bead18fbfb71e46b4193ac4464740f63fa4295 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 7 Oct 2025 16:49:22 -0700
Subject: [PATCH v2] Assert lwlocks not held during qual evaluation

Add assertions in the new push-down quals code that no lwlocks are
held, including lwlocks on buffers, during the evaluation of the
quals.
---
 src/backend/access/heap/heapam_valid.c |  2 ++
 src/backend/executor/nodeSeqscan.c     | 16 ++++++++++++++++
 src/backend/storage/lmgr/lwlock.c      |  9 +++++++++
 src/include/storage/lwlock.h           |  6 ++++++
 4 files changed, 33 insertions(+)

diff --git a/src/backend/access/heap/heapam_valid.c b/src/backend/access/heap/heapam_valid.c
index a05738a9144..00936331109 100644
--- a/src/backend/access/heap/heapam_valid.c
+++ b/src/backend/access/heap/heapam_valid.c
@@ -123,6 +123,8 @@ HeapKeyTest(HeapTuple tuple, TupleDesc tupdesc, int nkeys, ScanKey keys)
 	int			cur_nkeys = nkeys;
 	ScanKey		cur_key = keys;
 
+	AssertNoLWLockHeldByMe("HeapKeyTest");
+
 	for (; cur_nkeys--; cur_key++)
 	{
 		Datum		atp;
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 210d4cb84e0..ea80208b4ee 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -65,6 +65,8 @@ ExecSeqBuildScanKeys(PlanState *planstate, List *quals, int *numScanKeys,
 	int			n_runtime_keys;
 	int			max_runtime_keys;
 
+	AssertNoLWLockHeldByMe("ExecSeqBuildScanKeys");
+
 	n_quals = list_length(quals);
 
 	/*
@@ -396,6 +398,8 @@ SeqNext(SeqScanState *node)
 	ScanDirection direction;
 	TupleTableSlot *slot;
 
+	AssertNoLWLockHeldByMe("SeqNext");
+
 	/*
 	 * get information from the estate and scan state
 	 */
@@ -778,6 +782,8 @@ ExecReScanSeqScan(SeqScanState *node)
 {
 	TableScanDesc scan;
 
+	AssertNoLWLockHeldByMe("ExecReScanSeqScan");
+
 	/*
 	 * If we are doing runtime key calculations (ie, any of the scan key
 	 * values weren't simple Consts), compute the new key values.  But first,
@@ -821,6 +827,8 @@ ExecSeqScanEvalRuntimeKeys(ExprContext *econtext,
 	int			j;
 	MemoryContext oldContext;
 
+	AssertNoLWLockHeldByMe("ExecSeqScanEvalRuntimeKeys");
+
 	/* We want to keep the key values in per-tuple memory */
 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
 
@@ -881,6 +889,8 @@ ExecSeqScanEstimate(SeqScanState *node,
 {
 	EState	   *estate = node->ss.ps.state;
 
+	AssertNoLWLockHeldByMe("ExecSeqScanEstimate");
+
 	node->pscan_len = table_parallelscan_estimate(node->ss.ss_currentRelation,
 												  estate->es_snapshot);
 	shm_toc_estimate_chunk(&pcxt->estimator, node->pscan_len);
@@ -900,6 +910,8 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
 	EState	   *estate = node->ss.ps.state;
 	ParallelTableScanDesc pscan;
 
+	AssertNoLWLockHeldByMe("ExecSeqScanInitializeDSM");
+
 	pscan = shm_toc_allocate(pcxt->toc, node->pscan_len);
 	table_parallelscan_initialize(node->ss.ss_currentRelation,
 								  pscan,
@@ -931,6 +943,8 @@ ExecSeqScanReInitializeDSM(SeqScanState *node,
 {
 	ParallelTableScanDesc pscan;
 
+	AssertNoLWLockHeldByMe("ExecSeqScanReInitializeDSM");
+
 	pscan = node->ss.ss_currentScanDesc->rs_parallel;
 	table_parallelscan_reinitialize(node->ss.ss_currentRelation, pscan);
 }
@@ -947,6 +961,8 @@ ExecSeqScanInitializeWorker(SeqScanState *node,
 {
 	ParallelTableScanDesc pscan;
 
+	AssertNoLWLockHeldByMe("ExecSeqScanInitializeWorker");
+
 	pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
 	node->ss.ss_currentScanDesc =
 		table_beginscan_parallel(node->ss.ss_currentRelation, pscan,
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index ec9c345ffdf..3aed1e3de4c 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -743,6 +743,15 @@ GetLWLockIdentifier(uint32 classId, uint16 eventId)
 	return GetLWTrancheName(eventId);
 }
 
+#ifdef USE_ASSERT_CHECKING
+void
+AssertNoLWLockHeldByMe(const char *context)
+{
+	if (num_held_lwlocks > 0)
+		elog(ERROR, "In %s: holding %d lightweight locks", context, num_held_lwlocks);
+}
+#endif
+
 /*
  * Internal function that tries to atomically acquire the lwlock in the passed
  * in mode.
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 5e717765764..f786b84ba91 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -146,6 +146,12 @@ extern void InitLWLockAccess(void);
 
 extern const char *GetLWLockIdentifier(uint32 classId, uint16 eventId);
 
+#ifdef USE_ASSERT_CHECKING
+extern void AssertNoLWLockHeldByMe(const char *context);
+#else
+#define AssertNoLWLockHeldByMe(context)
+#endif
+
 /*
  * Extensions (or core code) can obtain an LWLocks by calling
  * RequestNamedLWLockTranche() during postmaster startup.  Subsequently,
-- 
2.39.5 (Apple Git-154)

