On Tue, Jul 5, 2022 at 8:54 AM Andres Freund <and...@anarazel.de> wrote:
> On 2022-07-04 14:55:43 +1200, Thomas Munro wrote:
> > > It's per-backend state at least and just used for assertions. We could 
> > > remove
> > > it. Or stop checking it in places where it could be set wrongly: 
> > > dshash_find()
> > > and dshash_detach() couldn't check anymore, but the rest of the assertions
> > > would still be valid afaics?
> >
> > Yeah, it's all for assertions... let's just remove it.  Those
> > assertions were useful to me at some stage in development but won't
> > hold as well as I thought, at least without widespread PG_FINALLY(),
> > which wouldn't be nice.
>
> Hm. I'd be inclined to at least add a few more
> Assert(!LWLockHeldByMe[InMode]()) style assertions. E.g. to
> dshash_find_or_insert().

Yeah, I was wondering about that, but it needs to check the whole 128
element lock array.  Hmm, yeah that seems OK for assertion builds.
Since there were 6 places with I-hold-no-lock assertions, I shoved the
loop into a function so I could do:

-               Assert(!status->hash_table->find_locked);
+               assert_no_lock_held_by_me(hash_table);

> > +     Assert(LWLockHeldByMe(PARTITION_LOCK(hash_table, partition_index)));
> >
> > -     hash_table->find_locked = false;
> > -     hash_table->find_exclusively_locked = false;
> >       LWLockRelease(PARTITION_LOCK(hash_table, partition_index));

> This LWLockHeldByMe() doesn't add much - the LWLockRelease() will error out if
> we don't hold the lock.

Duh.  Removed.
From da650f5dc0dda2155d088f56e2c062f7d10dec44 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.mu...@gmail.com>
Date: Mon, 4 Jul 2022 11:56:02 +1200
Subject: [PATCH v2] Fix lock assertions in dshash.c.

dshash.c maintained flags to track locking, in order to make assertions
that the dshash API was being used correctly.  Unfortunately the
interaction with ereport's non-local exits was not thought through
carefully enough and the flag could get out of sync with reality.  Since
these flags were primarily used for assertions that no lock was held, we
can get most of the intended effect with a loop over all LWLocks instead
(in assertion builds only).

This problem was noted by Tom and Andres while reviewing changes to
support the new shared memory stats system in release 15, the second
user of dshash.c.  On closer inspection, even the earlier typcache.c
code was not guaranteed to meet the asserted conditions, now that it's
been highlighted that even dsa_get_address() can throw (albeit in
unlikely circumstances).

Back-patch to 11, where dshash.c arrived.

Reported-by: Tom Lane <t...@sss.pgh.pa.us>
Reported-by: Andres Freund <and...@anarazel.de>
Reviewed-by: Kyotaro HORIGUCHI <horiguchi.kyot...@lab.ntt.co.jp>
Reviewed-by: Zhihong Yu <z...@yugabyte.com>
Reviewed-by: Andres Freund <and...@anarazel.de>
Discussion: https://postgr.es/m/20220311012712.botrpsikaufzt...@alap3.anarazel.de
---
 src/backend/lib/dshash.c | 54 +++++++++++++++-------------------------
 1 file changed, 20 insertions(+), 34 deletions(-)

diff --git a/src/backend/lib/dshash.c b/src/backend/lib/dshash.c
index ec454b4d65..90255a5eb9 100644
--- a/src/backend/lib/dshash.c
+++ b/src/backend/lib/dshash.c
@@ -110,8 +110,6 @@ struct dshash_table
 	dshash_table_control *control;	/* Control object in DSM. */
 	dsa_pointer *buckets;		/* Current bucket pointers in DSM. */
 	size_t		size_log2;		/* log2(number of buckets) */
-	bool		find_locked;	/* Is any partition lock held by 'find'? */
-	bool		find_exclusively_locked;	/* ... exclusively? */
 };
 
 /* Given a pointer to an item, find the entry (user data) it holds. */
@@ -167,6 +165,7 @@ struct dshash_table
 		BUCKET_INDEX_FOR_HASH_AND_SIZE(hash,							\
 									   hash_table->size_log2)])
 
+static inline void assert_no_lock_held_by_me(dshash_table *hash_table);
 static void delete_item(dshash_table *hash_table,
 						dshash_table_item *item);
 static void resize(dshash_table *hash_table, size_t new_size);
@@ -234,9 +233,6 @@ dshash_create(dsa_area *area, const dshash_parameters *params, void *arg)
 		}
 	}
 
-	hash_table->find_locked = false;
-	hash_table->find_exclusively_locked = false;
-
 	/*
 	 * Set up the initial array of buckets.  Our initial size is the same as
 	 * the number of partitions.
@@ -285,8 +281,6 @@ dshash_attach(dsa_area *area, const dshash_parameters *params,
 	hash_table->params = *params;
 	hash_table->arg = arg;
 	hash_table->control = dsa_get_address(area, control);
-	hash_table->find_locked = false;
-	hash_table->find_exclusively_locked = false;
 	Assert(hash_table->control->magic == DSHASH_MAGIC);
 
 	/*
@@ -309,7 +303,7 @@ dshash_attach(dsa_area *area, const dshash_parameters *params,
 void
 dshash_detach(dshash_table *hash_table)
 {
-	Assert(!hash_table->find_locked);
+	assert_no_lock_held_by_me(hash_table);
 
 	/* The hash table may have been destroyed.  Just free local memory. */
 	pfree(hash_table);
@@ -400,7 +394,7 @@ dshash_find(dshash_table *hash_table, const void *key, bool exclusive)
 	partition = PARTITION_FOR_HASH(hash);
 
 	Assert(hash_table->control->magic == DSHASH_MAGIC);
-	Assert(!hash_table->find_locked);
+	assert_no_lock_held_by_me(hash_table);
 
 	LWLockAcquire(PARTITION_LOCK(hash_table, partition),
 				  exclusive ? LW_EXCLUSIVE : LW_SHARED);
@@ -418,8 +412,6 @@ dshash_find(dshash_table *hash_table, const void *key, bool exclusive)
 	else
 	{
 		/* The caller will free the lock by calling dshash_release_lock. */
-		hash_table->find_locked = true;
-		hash_table->find_exclusively_locked = exclusive;
 		return ENTRY_FROM_ITEM(item);
 	}
 }
@@ -449,7 +441,7 @@ dshash_find_or_insert(dshash_table *hash_table,
 	partition = &hash_table->control->partitions[partition_index];
 
 	Assert(hash_table->control->magic == DSHASH_MAGIC);
-	Assert(!hash_table->find_locked);
+	assert_no_lock_held_by_me(hash_table);
 
 restart:
 	LWLockAcquire(PARTITION_LOCK(hash_table, partition_index),
@@ -494,8 +486,6 @@ restart:
 	}
 
 	/* The caller must release the lock with dshash_release_lock. */
-	hash_table->find_locked = true;
-	hash_table->find_exclusively_locked = true;
 	return ENTRY_FROM_ITEM(item);
 }
 
@@ -514,7 +504,7 @@ dshash_delete_key(dshash_table *hash_table, const void *key)
 	bool		found;
 
 	Assert(hash_table->control->magic == DSHASH_MAGIC);
-	Assert(!hash_table->find_locked);
+	assert_no_lock_held_by_me(hash_table);
 
 	hash = hash_key(hash_table, key);
 	partition = PARTITION_FOR_HASH(hash);
@@ -551,14 +541,10 @@ dshash_delete_entry(dshash_table *hash_table, void *entry)
 	size_t		partition = PARTITION_FOR_HASH(item->hash);
 
 	Assert(hash_table->control->magic == DSHASH_MAGIC);
-	Assert(hash_table->find_locked);
-	Assert(hash_table->find_exclusively_locked);
 	Assert(LWLockHeldByMeInMode(PARTITION_LOCK(hash_table, partition),
 								LW_EXCLUSIVE));
 
 	delete_item(hash_table, item);
-	hash_table->find_locked = false;
-	hash_table->find_exclusively_locked = false;
 	LWLockRelease(PARTITION_LOCK(hash_table, partition));
 }
 
@@ -572,13 +558,7 @@ dshash_release_lock(dshash_table *hash_table, void *entry)
 	size_t		partition_index = PARTITION_FOR_HASH(item->hash);
 
 	Assert(hash_table->control->magic == DSHASH_MAGIC);
-	Assert(hash_table->find_locked);
-	Assert(LWLockHeldByMeInMode(PARTITION_LOCK(hash_table, partition_index),
-								hash_table->find_exclusively_locked
-								? LW_EXCLUSIVE : LW_SHARED));
 
-	hash_table->find_locked = false;
-	hash_table->find_exclusively_locked = false;
 	LWLockRelease(PARTITION_LOCK(hash_table, partition_index));
 }
 
@@ -644,7 +624,7 @@ dshash_seq_next(dshash_seq_status *status)
 	if (status->curpartition == -1)
 	{
 		Assert(status->curbucket == 0);
-		Assert(!status->hash_table->find_locked);
+		assert_no_lock_held_by_me(status->hash_table);
 
 		status->curpartition = 0;
 
@@ -702,8 +682,6 @@ dshash_seq_next(dshash_seq_status *status)
 
 	status->curitem =
 		dsa_get_address(status->hash_table->area, next_item_pointer);
-	status->hash_table->find_locked = true;
-	status->hash_table->find_exclusively_locked = status->exclusive;
 
 	/*
 	 * The caller may delete the item. Store the next item in case of
@@ -722,9 +700,6 @@ dshash_seq_next(dshash_seq_status *status)
 void
 dshash_seq_term(dshash_seq_status *status)
 {
-	status->hash_table->find_locked = false;
-	status->hash_table->find_exclusively_locked = false;
-
 	if (status->curpartition >= 0)
 		LWLockRelease(PARTITION_LOCK(status->hash_table, status->curpartition));
 }
@@ -743,8 +718,6 @@ dshash_delete_current(dshash_seq_status *status)
 
 	Assert(status->exclusive);
 	Assert(hash_table->control->magic == DSHASH_MAGIC);
-	Assert(hash_table->find_locked);
-	Assert(hash_table->find_exclusively_locked);
 	Assert(LWLockHeldByMeInMode(PARTITION_LOCK(hash_table, partition),
 								LW_EXCLUSIVE));
 
@@ -762,7 +735,7 @@ dshash_dump(dshash_table *hash_table)
 	size_t		j;
 
 	Assert(hash_table->control->magic == DSHASH_MAGIC);
-	Assert(!hash_table->find_locked);
+	assert_no_lock_held_by_me(hash_table);
 
 	for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
 	{
@@ -806,6 +779,19 @@ dshash_dump(dshash_table *hash_table)
 		LWLockRelease(PARTITION_LOCK(hash_table, i));
 }
 
+/*
+ * Assertion used to enforce rule that you can't lock more than one partition
+ * at a time, to avoid risk of deadlock.
+ */
+static inline void
+assert_no_lock_held_by_me(dshash_table *hash_table)
+{
+	int			i;
+
+	for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i)
+		Assert(!LWLockHeldByMe(PARTITION_LOCK(hash_table, i)));
+}
+
 /*
  * Delete a locked item to which we have a pointer.
  */
-- 
2.36.1

Reply via email to