public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v10 1/3] Allow CREATE INDEX CONCURRENTLY on partitioned table 7+ messages / 5 participants [nested] [flat]
* [PATCH v10 1/3] Allow CREATE INDEX CONCURRENTLY on partitioned table @ 2020-06-06 22:42 Justin Pryzby <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Justin Pryzby @ 2020-06-06 22:42 UTC (permalink / raw) Note, this effectively reverts 050098b14, so take care to not reintroduce the bug it fixed. XXX: does pgstat_progress_update_param() break other commands progress ? --- doc/src/sgml/ref/create_index.sgml | 9 -- src/backend/commands/indexcmds.c | 141 ++++++++++++++++++------- src/test/regress/expected/indexing.out | 60 ++++++++++- src/test/regress/sql/indexing.sql | 18 +++- 4 files changed, 172 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml index 749db2845e..ba4424d379 100644 --- a/doc/src/sgml/ref/create_index.sgml +++ b/doc/src/sgml/ref/create_index.sgml @@ -661,15 +661,6 @@ Indexes: cannot. </para> - <para> - Concurrent builds for indexes on partitioned tables are currently not - supported. However, you may concurrently build the index on each - partition individually and then finally create the partitioned index - non-concurrently in order to reduce the time where writes to the - partitioned table will be locked out. In this case, building the - partitioned index is a metadata only operation. - </para> - </refsect2> </refsect1> diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 996f1ed070..c1dd4c8362 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -93,6 +93,7 @@ static bool ReindexRelationConcurrently(Oid relationOid, int options); static void ReindexPartitions(Oid relid, int options, bool isTopLevel); static void ReindexMultipleInternal(List *relids, int options); +static void reindex_invalid_child_indexes(Oid indexRelationId); static void reindex_error_callback(void *args); static void update_relispartition(Oid relationId, bool newval); static bool CompareOpclassOptions(Datum *opts1, Datum *opts2, int natts); @@ -667,17 +668,6 @@ DefineIndex(Oid relationId, partitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE; if (partitioned) { - /* - * Note: we check 'stmt->concurrent' rather than 'concurrent', so that - * the error is thrown also for temporary tables. Seems better to be - * consistent, even though we could do it on temporary table because - * we're not actually doing it concurrently. - */ - if (stmt->concurrent) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot create index on partitioned table \"%s\" concurrently", - RelationGetRelationName(rel)))); if (stmt->excludeOpNames) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -1111,6 +1101,11 @@ DefineIndex(Oid relationId, if (pd->nparts != 0) flags |= INDEX_CREATE_INVALID; } + else if (concurrent && OidIsValid(parentIndexId)) + { + /* If concurrent, initially build index partitions as "invalid" */ + flags |= INDEX_CREATE_INVALID; + } if (stmt->deferrable) constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE; @@ -1163,6 +1158,14 @@ DefineIndex(Oid relationId, */ if (!stmt->relation || stmt->relation->inh) { + /* + * Need to close the relation before recursing into children, so + * copy needed data into a longlived context. + */ + + MemoryContext ind_context = AllocSetContextCreate(PortalContext, "CREATE INDEX", + ALLOCSET_DEFAULT_SIZES); + MemoryContext oldcontext = MemoryContextSwitchTo(ind_context); PartitionDesc partdesc = RelationGetPartitionDesc(rel); int nparts = partdesc->nparts; Oid *part_oids = palloc(sizeof(Oid) * nparts); @@ -1170,12 +1173,15 @@ DefineIndex(Oid relationId, TupleDesc parentDesc; Oid *opfamOids; + // If concurrent, maybe this should be done after excluding indexes which already exist ? pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_TOTAL, nparts); memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts); + parentDesc = CreateTupleDescCopy(RelationGetDescr(rel)); + table_close(rel, NoLock); + MemoryContextSwitchTo(oldcontext); - parentDesc = RelationGetDescr(rel); opfamOids = palloc(sizeof(Oid) * numberOfKeyAttributes); for (i = 0; i < numberOfKeyAttributes; i++) opfamOids[i] = get_opclass_family(classObjectId[i]); @@ -1218,10 +1224,12 @@ DefineIndex(Oid relationId, continue; } + oldcontext = MemoryContextSwitchTo(ind_context); childidxs = RelationGetIndexList(childrel); attmap = build_attrmap_by_name(RelationGetDescr(childrel), parentDesc); + MemoryContextSwitchTo(oldcontext); foreach(cell, childidxs) { @@ -1292,10 +1300,14 @@ DefineIndex(Oid relationId, */ if (!found) { - IndexStmt *childStmt = copyObject(stmt); + IndexStmt *childStmt; bool found_whole_row; ListCell *lc; + oldcontext = MemoryContextSwitchTo(ind_context); + childStmt = copyObject(stmt); + MemoryContextSwitchTo(oldcontext); + /* * We can't use the same index name for the child index, * so clear idxname to let the recursive invocation choose @@ -1347,10 +1359,18 @@ DefineIndex(Oid relationId, createdConstraintId, is_alter_table, check_rights, check_not_in_use, skip_build, quiet); + if (concurrent) + { + PopActiveSnapshot(); + PushActiveSnapshot(GetTransactionSnapshot()); + invalidate_parent = true; + } } - pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_DONE, - i + 1); + /* For concurrent build, this is a catalog-only stage */ + if (!concurrent) + pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_DONE, + i + 1); free_attrmap(attmap); } @@ -1360,51 +1380,42 @@ DefineIndex(Oid relationId, * invalid, this is incorrect, so update our row to invalid too. */ if (invalidate_parent) - { - Relation pg_index = table_open(IndexRelationId, RowExclusiveLock); - HeapTuple tup, - newtup; - - tup = SearchSysCache1(INDEXRELID, - ObjectIdGetDatum(indexRelationId)); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for index %u", - indexRelationId); - newtup = heap_copytuple(tup); - ((Form_pg_index) GETSTRUCT(newtup))->indisvalid = false; - CatalogTupleUpdate(pg_index, &tup->t_self, newtup); - ReleaseSysCache(tup); - table_close(pg_index, RowExclusiveLock); - heap_freetuple(newtup); - } - } + index_set_state_flags(indexRelationId, INDEX_DROP_CLEAR_VALID); + } else + table_close(rel, NoLock); /* * Indexes on partitioned tables are not themselves built, so we're * done here. */ - table_close(rel, NoLock); if (!OidIsValid(parentIndexId)) + { + if (concurrent) + reindex_invalid_child_indexes(indexRelationId); + pgstat_progress_end_command(); + } + return address; } - if (!concurrent) + table_close(rel, NoLock); + if (!concurrent || OidIsValid(parentIndexId)) { - /* Close the heap and we're done, in the non-concurrent case */ - table_close(rel, NoLock); + /* + * We're done if this is the top-level index, + * or the catalog-only phase of a partition built concurrently + */ - /* If this is the top-level index, we're done. */ if (!OidIsValid(parentIndexId)) pgstat_progress_end_command(); return address; } - /* save lockrelid and locktag for below, then close rel */ + /* save lockrelid and locktag for below */ heaprelid = rel->rd_lockInfo.lockRelId; SET_LOCKTAG_RELATION(heaplocktag, heaprelid.dbId, heaprelid.relId); - table_close(rel, NoLock); /* * For a concurrent build, it's important to make the catalog entries @@ -1586,6 +1597,56 @@ DefineIndex(Oid relationId, return address; } +/* Reindex invalid child indexes created earlier */ +static void +reindex_invalid_child_indexes(Oid indexRelationId) +{ + ListCell *lc; + int npart = 0; + + MemoryContext ind_context = AllocSetContextCreate(PortalContext, "CREATE INDEX", + ALLOCSET_DEFAULT_SIZES); + MemoryContext oldcontext; + List *childs = find_inheritance_children(indexRelationId, ShareLock); + List *partitions = NIL; + + PreventInTransactionBlock(true, "REINDEX INDEX"); + + foreach (lc, childs) + { + Oid partoid = lfirst_oid(lc); + + pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_DONE, + npart++); + + if (get_index_isvalid(partoid) || + !RELKIND_HAS_STORAGE(get_rel_relkind(partoid))) + continue; + + /* Save partition OID */ + oldcontext = MemoryContextSwitchTo(ind_context); + partitions = lappend_oid(partitions, partoid); + MemoryContextSwitchTo(oldcontext); + } + + /* + * Process each partition listed in a separate transaction. Note that + * this commits and then starts a new transaction immediately. + */ + ReindexMultipleInternal(partitions, REINDEXOPT_CONCURRENTLY); + + /* + * CIC needs to mark a partitioned index as VALID, which itself + * requires setting READY, which is unset for CIC (even though + * it's meaningless for an index without storage). + * This must be done only while holding a lock which precludes adding + * partitions. + * See also: validatePartitionedIndex(). + */ + index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY); + CommandCounterIncrement(); + index_set_state_flags(indexRelationId, INDEX_CREATE_SET_VALID); +} /* * CheckMutability diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out index 49b6f7c18f..2fb00a2fa7 100644 --- a/src/test/regress/expected/indexing.out +++ b/src/test/regress/expected/indexing.out @@ -50,11 +50,63 @@ select relname, relkind, relhassubclass, inhparent::regclass (8 rows) drop table idxpart; --- Some unsupported features +-- CIC on partitioned table create table idxpart (a int, b int, c text) partition by range (a); -create table idxpart1 partition of idxpart for values from (0) to (10); -create index concurrently on idxpart (a); -ERROR: cannot create index on partitioned table "idxpart" concurrently +create table idxpart1 partition of idxpart for values from (0) to (10) partition by range(a); +create table idxpart11 partition of idxpart1 for values from (0) to (10) partition by range(a); +create table idxpart111 partition of idxpart11 default partition by range(a); +create table idxpart1111 partition of idxpart111 default partition by range(a); +create table idxpart2 partition of idxpart for values from (10) to (20); +insert into idxpart2 values(10),(10); -- not unique +create index concurrently on idxpart (a); -- partitioned +create index concurrently on idxpart1 (a); -- partitioned and partition +create index concurrently on idxpart11 (a); -- partitioned and partition, with no leaves +create index concurrently on idxpart2 (a); -- leaf +create unique index concurrently on idxpart (a); -- partitioned, unique failure +ERROR: could not create unique index "idxpart2_a_idx2_ccnew" +DETAIL: Key (a)=(10) is duplicated. +\d idxpart + Partitioned table "public.idxpart" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + c | text | | | +Partition key: RANGE (a) +Indexes: + "idxpart_a_idx" btree (a) + "idxpart_a_idx1" UNIQUE, btree (a) INVALID +Number of partitions: 2 (Use \d+ to list them.) + +\d idxpart1 + Partitioned table "public.idxpart1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + c | text | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10) +Partition key: RANGE (a) +Indexes: + "idxpart1_a_idx" btree (a) INVALID + "idxpart1_a_idx1" btree (a) + "idxpart1_a_idx2" UNIQUE, btree (a) INVALID +Number of partitions: 1 (Use \d+ to list them.) + +\d idxpart2 + Table "public.idxpart2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + c | text | | | +Partition of: idxpart FOR VALUES FROM (10) TO (20) +Indexes: + "idxpart2_a_idx" btree (a) + "idxpart2_a_idx1" btree (a) + "idxpart2_a_idx2" UNIQUE, btree (a) INVALID + "idxpart2_a_idx2_ccnew" UNIQUE, btree (a) INVALID + drop table idxpart; -- Verify bugfix with query on indexed partitioned table with no partitions -- https://postgr.es/m/[email protected] diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql index fc1479dca6..610c12de9e 100644 --- a/src/test/regress/sql/indexing.sql +++ b/src/test/regress/sql/indexing.sql @@ -29,10 +29,22 @@ select relname, relkind, relhassubclass, inhparent::regclass where relname like 'idxpart%' order by relname; drop table idxpart; --- Some unsupported features +-- CIC on partitioned table create table idxpart (a int, b int, c text) partition by range (a); -create table idxpart1 partition of idxpart for values from (0) to (10); -create index concurrently on idxpart (a); +create table idxpart1 partition of idxpart for values from (0) to (10) partition by range(a); +create table idxpart11 partition of idxpart1 for values from (0) to (10) partition by range(a); +create table idxpart111 partition of idxpart11 default partition by range(a); +create table idxpart1111 partition of idxpart111 default partition by range(a); +create table idxpart2 partition of idxpart for values from (10) to (20); +insert into idxpart2 values(10),(10); -- not unique +create index concurrently on idxpart (a); -- partitioned +create index concurrently on idxpart1 (a); -- partitioned and partition +create index concurrently on idxpart11 (a); -- partitioned and partition, with no leaves +create index concurrently on idxpart2 (a); -- leaf +create unique index concurrently on idxpart (a); -- partitioned, unique failure +\d idxpart +\d idxpart1 +\d idxpart2 drop table idxpart; -- Verify bugfix with query on indexed partitioned table with no partitions -- 2.17.0 --i3lJ51RuaGWuFYNw Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v10-0002-Add-SKIPVALID-flag-for-more-integration.patch" ^ permalink raw reply [nested|flat] 7+ messages in thread
* Latches vs lwlock contention @ 2022-10-28 03:56 Thomas Munro <[email protected]> 0 siblings, 2 replies; 7+ messages in thread From: Thomas Munro @ 2022-10-28 03:56 UTC (permalink / raw) To: pgsql-hackers; +Cc: Yura Sokolov <[email protected]>; Andres Freund <[email protected]> Hi, We usually want to release lwlocks, and definitely spinlocks, before calling SetLatch(), to avoid putting a system call into the locked region so that we minimise the time held. There are a few places where we don't do that, possibly because it's not just a simple latch to hold a pointer to but rather a set of them that needs to be collected from some data structure and we don't have infrastructure to help with that. There are also cases where we semi-reliably create lock contention, because the backends that wake up immediately try to acquire the very same lock. One example is heavyweight lock wakeups. If you run BEGIN; LOCK TABLE t; ... and then N other sessions wait in SELECT * FROM t;, and then you run ... COMMIT;, you'll see the first session wake all the others while it still holds the partition lock itself. They'll all wake up and begin to re-acquire the same partition lock in exclusive mode, immediately go back to sleep on *that* wait list, and then wake each other up one at a time in a chain. We could avoid the first double-bounce by not setting the latches until after we've released the partition lock. We could avoid the rest of them by not re-acquiring the partition lock at all, which ... if I'm reading right ... shouldn't actually be necessary in modern PostgreSQL? Or if there is another reason to re-acquire then maybe the comment should be updated. Presumably no one really does that repeatedly while there is a long queue of non-conflicting waiters, so I'm not claiming it's a major improvement, but it's at least a micro-optimisation. There are some other simpler mechanical changes including synchronous replication, SERIALIZABLE DEFERRABLE and condition variables (this one inspired by Yura Sokolov's patches[1]). Actually I'm not at all sure about the CV implementation, I feel like a more ambitious change is needed to make our CVs perform. See attached sketch patches. I guess the main thing that may not be good enough is the use of a fixed sized latch buffer. Memory allocation in don't-throw-here environments like the guts of lock code might be an issue, which is why it just gives up and flushes when full; maybe it should try to allocate and fall back to flushing only if that fails. These sketch patches aren't proposals, just observations in need of more study. [1] https://postgr.es/m/1edbb61981fe1d99c3f20e3d56d6c88999f4227c.camel%40postgrespro.ru Attachments: [text/x-patch] 0001-Provide-SetLatches-for-batched-deferred-latches.patch (8.8K, ../../CA+hUKGKmO7ze0Z6WXKdrLxmvYa=zVGGXOO30MMktufofVwEm1A@mail.gmail.com/2-0001-Provide-SetLatches-for-batched-deferred-latches.patch) download | inline diff: From b64d5782e2c3a2e34274a3bf9df4449afaee94dc Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Wed, 26 Oct 2022 15:51:45 +1300 Subject: [PATCH 1/8] Provide SetLatches() for batched deferred latches. If we have a way to buffer a set of wakeup targets and process them at a later time, we can: * move SetLatch() system calls out from under LWLocks, so that locks can be released faster; this is especially interesting in cases where the target backends will immediately try to acquire the same lock, or generally when the lock is heavily contended * possibly gain some micro-opimization from issuing only two memory barriers for the whole batch of latches, not two for each latch to be set * provide the opportunity for potential future latch implementation mechanisms to deliver wakeups in a single system call Individual users of this facility will follow in separate patches. --- src/backend/storage/ipc/latch.c | 187 ++++++++++++++++++------------- src/include/storage/latch.h | 13 +++ src/tools/pgindent/typedefs.list | 1 + 3 files changed, 123 insertions(+), 78 deletions(-) diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c index eb3a569aae..71fdc388c8 100644 --- a/src/backend/storage/ipc/latch.c +++ b/src/backend/storage/ipc/latch.c @@ -576,105 +576,136 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock, } /* - * Sets a latch and wakes up anyone waiting on it. - * - * This is cheap if the latch is already set, otherwise not so much. - * - * NB: when calling this in a signal handler, be sure to save and restore - * errno around it. (That's standard practice in most signal handlers, of - * course, but we used to omit it in handlers that only set a flag.) - * - * NB: this function is called from critical sections and signal handlers so - * throwing an error is not a good idea. + * Set multiple latches at the same time. + * Note: modifies input array. */ -void -SetLatch(Latch *latch) +static void +SetLatchV(Latch **latches, int nlatches) { -#ifndef WIN32 - pid_t owner_pid; -#else - HANDLE handle; -#endif - - /* - * The memory barrier has to be placed here to ensure that any flag - * variables possibly changed by this process have been flushed to main - * memory, before we check/set is_set. - */ + /* Flush any other changes out to main memory just once. */ pg_memory_barrier(); - /* Quick exit if already set */ - if (latch->is_set) - return; + /* Keep only latches that are not already set, and set them. */ + for (int i = 0; i < nlatches; ++i) + { + Latch *latch = latches[i]; - latch->is_set = true; + if (!latch->is_set) + latch->is_set = true; + else + latches[i] = NULL; + } pg_memory_barrier(); - if (!latch->maybe_sleeping) - return; + /* Wake the remaining latches that might be sleeping. */ #ifndef WIN32 - - /* - * See if anyone's waiting for the latch. It can be the current process if - * we're in a signal handler. We use the self-pipe or SIGURG to ourselves - * to wake up WaitEventSetWaitBlock() without races in that case. If it's - * another process, send a signal. - * - * Fetch owner_pid only once, in case the latch is concurrently getting - * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't - * guaranteed to be true! In practice, the effective range of pid_t fits - * in a 32 bit integer, and so should be atomic. In the worst case, we - * might end up signaling the wrong process. Even then, you're very - * unlucky if a process with that bogus pid exists and belongs to - * Postgres; and PG database processes should handle excess SIGUSR1 - * interrupts without a problem anyhow. - * - * Another sort of race condition that's possible here is for a new - * process to own the latch immediately after we look, so we don't signal - * it. This is okay so long as all callers of ResetLatch/WaitLatch follow - * the standard coding convention of waiting at the bottom of their loops, - * not the top, so that they'll correctly process latch-setting events - * that happen before they enter the loop. - */ - owner_pid = latch->owner_pid; - if (owner_pid == 0) - return; - else if (owner_pid == MyProcPid) + for (int i = 0; i < nlatches; ++i) { + Latch *latch = latches[i]; + pid_t owner_pid; + + if (!latch || !latch->maybe_sleeping) + continue; + + /* + * See if anyone's waiting for the latch. It can be the current process + * if we're in a signal handler. We use the self-pipe or SIGURG to + * ourselves to wake up WaitEventSetWaitBlock() without races in that + * case. If it's another process, send a signal. + * + * Fetch owner_pid only once, in case the latch is concurrently getting + * owned or disowned. XXX: This assumes that pid_t is atomic, which + * isn't guaranteed to be true! In practice, the effective range of + * pid_t fits in a 32 bit integer, and so should be atomic. In the + * worst case, we might end up signaling the wrong process. Even then, + * you're very unlucky if a process with that bogus pid exists and + * belongs to Postgres; and PG database processes should handle excess + * SIGURG interrupts without a problem anyhow. + * + * Another sort of race condition that's possible here is for a new + * process to own the latch immediately after we look, so we don't + * signal it. This is okay so long as all callers of + * ResetLatch/WaitLatch follow the standard coding convention of + * waiting at the bottom of their loops, not the top, so that they'll + * correctly process latch-setting events that happen before they enter + * the loop. + */ + owner_pid = latch->owner_pid; + + if (owner_pid == MyProcPid) + { + if (waiting) + { #if defined(WAIT_USE_SELF_PIPE) - if (waiting) - sendSelfPipeByte(); + sendSelfPipeByte(); #else - if (waiting) - kill(MyProcPid, SIGURG); + kill(MyProcPid, SIGURG); #endif + } + } + else + kill(owner_pid, SIGURG); } - else - kill(owner_pid, SIGURG); - #else - - /* - * See if anyone's waiting for the latch. It can be the current process if - * we're in a signal handler. - * - * Use a local variable here just in case somebody changes the event field - * concurrently (which really should not happen). - */ - handle = latch->event; - if (handle) + for (int i = 0; i < nlatches; ++i) { - SetEvent(handle); + Latch *latch = latches[i]; - /* - * Note that we silently ignore any errors. We might be in a signal - * handler or other critical path where it's not safe to call elog(). - */ + if (latch && latch->maybe_sleeping) + { + HANDLE event = latch->event; + + if (event) + SetEvent(event); + } } #endif } +/* + * Sets a latch and wakes up anyone waiting on it. + * + * This is cheap if the latch is already set, otherwise not so much. + * + * NB: when calling this in a signal handler, be sure to save and restore + * errno around it. (That's standard practice in most signal handlers, of + * course, but we used to omit it in handlers that only set a flag.) + * + * NB: this function is called from critical sections and signal handlers so + * throwing an error is not a good idea. + */ +void +SetLatch(Latch *latch) +{ + SetLatchV(&latch, 1); +} + +/* + * Add a latch to a batch, to be set later as a group. + */ +void +AddLatch(LatchBatch *batch, Latch *latch) +{ + if (batch->size == lengthof(batch->latches)) + SetLatches(batch); + + batch->latches[batch->size++] = latch; +} + +/* + * Set all the latches accumulated in 'batch'. + */ +void +SetLatches(LatchBatch *batch) +{ + if (batch->size > 0) + { + SetLatchV(batch->latches, batch->size); + batch->size = 0; + } +} + /* * Clear the latch. Calling WaitLatch after this will sleep, unless * the latch is set again before the WaitLatch call. diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h index 68ab740f16..0edf364637 100644 --- a/src/include/storage/latch.h +++ b/src/include/storage/latch.h @@ -118,6 +118,17 @@ typedef struct Latch #endif } Latch; +#define LATCH_BATCH_SIZE 64 + +/* + * Container for setting multiple latches at a time. + */ +typedef struct LatchBatch +{ + int size; + Latch *latches[LATCH_BATCH_SIZE]; +} LatchBatch; + /* * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or * WaitEventSetWait(). @@ -163,6 +174,8 @@ extern void InitSharedLatch(Latch *latch); extern void OwnLatch(Latch *latch); extern void DisownLatch(Latch *latch); extern void SetLatch(Latch *latch); +extern void AddLatch(LatchBatch * batch, Latch *latch); +extern void SetLatches(LatchBatch * batch); extern void ResetLatch(Latch *latch); extern void ShutdownLatchSupport(void); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 2f02cc8f42..f05c41043d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1389,6 +1389,7 @@ LagTracker LargeObjectDesc LastAttnumInfo Latch +LatchBatch LerpFunc LexDescr LexemeEntry -- 2.35.1 [text/x-patch] 0002-Use-SetLatches-for-condition-variables.patch (7.8K, ../../CA+hUKGKmO7ze0Z6WXKdrLxmvYa=zVGGXOO30MMktufofVwEm1A@mail.gmail.com/3-0002-Use-SetLatches-for-condition-variables.patch) download | inline diff: From 7d68b9bad4e172fc5d56df8630af2c22986b4f81 Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Wed, 26 Oct 2022 17:36:34 +1300 Subject: [PATCH 2/8] Use SetLatches() for condition variables. Drain condition variable wait lists in larger batches, not one at a time, while broadcasting. This is an idea from Yura Sokolov, which I've now combined with SetLatches() for slightly more efficiency. Since we're now performing loops, change the internal spinlock to an lwlock. XXX There is probably a better data structure/arrangement that would allow us to hold the lock for a shorter time. This patch is not very satisfying yet. Discussion: https://postgr.es/m/1edbb61981fe1d99c3f20e3d56d6c88999f4227c.camel%40postgrespro.ru --- src/backend/storage/lmgr/condition_variable.c | 97 +++++++++---------- src/backend/storage/lmgr/lwlock.c | 2 + src/include/storage/condition_variable.h | 4 +- src/include/storage/lwlock.h | 1 + 4 files changed, 49 insertions(+), 55 deletions(-) diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c index de65dac3ae..4b9749ecdd 100644 --- a/src/backend/storage/lmgr/condition_variable.c +++ b/src/backend/storage/lmgr/condition_variable.c @@ -36,7 +36,7 @@ static ConditionVariable *cv_sleep_target = NULL; void ConditionVariableInit(ConditionVariable *cv) { - SpinLockInit(&cv->mutex); + LWLockInitialize(&cv->mutex, LWTRANCHE_CONDITION_VARIABLE); proclist_init(&cv->wakeup); } @@ -74,9 +74,9 @@ ConditionVariablePrepareToSleep(ConditionVariable *cv) cv_sleep_target = cv; /* Add myself to the wait queue. */ - SpinLockAcquire(&cv->mutex); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink); - SpinLockRelease(&cv->mutex); + LWLockRelease(&cv->mutex); } /* @@ -180,13 +180,13 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout, * by something other than ConditionVariableSignal; though we don't * guarantee not to return spuriously, we'll avoid this obvious case. */ - SpinLockAcquire(&cv->mutex); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink)) { done = true; proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink); } - SpinLockRelease(&cv->mutex); + LWLockRelease(&cv->mutex); /* * Check for interrupts, and return spuriously if that caused the @@ -233,12 +233,12 @@ ConditionVariableCancelSleep(void) if (cv == NULL) return; - SpinLockAcquire(&cv->mutex); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); if (proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink)) proclist_delete(&cv->wakeup, MyProc->pgprocno, cvWaitLink); else signaled = true; - SpinLockRelease(&cv->mutex); + LWLockRelease(&cv->mutex); /* * If we've received a signal, pass it on to another waiting process, if @@ -265,10 +265,10 @@ ConditionVariableSignal(ConditionVariable *cv) PGPROC *proc = NULL; /* Remove the first process from the wakeup queue (if any). */ - SpinLockAcquire(&cv->mutex); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); if (!proclist_is_empty(&cv->wakeup)) proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink); - SpinLockRelease(&cv->mutex); + LWLockRelease(&cv->mutex); /* If we found someone sleeping, set their latch to wake them up. */ if (proc != NULL) @@ -287,6 +287,7 @@ ConditionVariableBroadcast(ConditionVariable *cv) { int pgprocno = MyProc->pgprocno; PGPROC *proc = NULL; + bool inserted_sentinel = false; bool have_sentinel = false; /* @@ -313,52 +314,42 @@ ConditionVariableBroadcast(ConditionVariable *cv) if (cv_sleep_target != NULL) ConditionVariableCancelSleep(); - /* - * Inspect the state of the queue. If it's empty, we have nothing to do. - * If there's exactly one entry, we need only remove and signal that - * entry. Otherwise, remove the first entry and insert our sentinel. - */ - SpinLockAcquire(&cv->mutex); - /* While we're here, let's assert we're not in the list. */ - Assert(!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink)); - - if (!proclist_is_empty(&cv->wakeup)) + do { - proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink); - if (!proclist_is_empty(&cv->wakeup)) - { - proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink); - have_sentinel = true; - } - } - SpinLockRelease(&cv->mutex); - - /* Awaken first waiter, if there was one. */ - if (proc != NULL) - SetLatch(&proc->procLatch); + int max_batch_size = Min(LATCH_BATCH_SIZE, 8); /* XXX */ + LatchBatch batch = {0}; - while (have_sentinel) - { - /* - * Each time through the loop, remove the first wakeup list entry, and - * signal it unless it's our sentinel. Repeat as long as the sentinel - * remains in the list. - * - * Notice that if someone else removes our sentinel, we will waken one - * additional process before exiting. That's intentional, because if - * someone else signals the CV, they may be intending to waken some - * third process that added itself to the list after we added the - * sentinel. Better to give a spurious wakeup (which should be - * harmless beyond wasting some cycles) than to lose a wakeup. - */ - proc = NULL; - SpinLockAcquire(&cv->mutex); - if (!proclist_is_empty(&cv->wakeup)) + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); + while (!proclist_is_empty(&cv->wakeup)) + { + if (batch.size == max_batch_size) + { + if (!inserted_sentinel) + { + /* First batch of many. Add sentinel. */ + proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink); + inserted_sentinel = true; + have_sentinel = true; + } + break; + } proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink); - have_sentinel = proclist_contains(&cv->wakeup, pgprocno, cvWaitLink); - SpinLockRelease(&cv->mutex); + if (proc == MyProc) + { + /* We hit our sentinel. We're done. */ + have_sentinel = false; + break; + } + else if (!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink)) + { + /* Someone else hit our sentinel. We're done. */ + have_sentinel = false; + } + AddLatch(&batch, &proc->procLatch); + } + LWLockRelease(&cv->mutex); - if (proc != NULL && proc != MyProc) - SetLatch(&proc->procLatch); - } + /* Awaken this batch of waiters, if there were some. */ + SetLatches(&batch); + } while (have_sentinel); } diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index d274c9b1dc..03186da0a3 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -183,6 +183,8 @@ static const char *const BuiltinTrancheNames[] = { "PgStatsHash", /* LWTRANCHE_PGSTATS_DATA: */ "PgStatsData", + /* LWTRANCHE_CONDITION_VARIABLE: */ + "ConditionVariable", }; StaticAssertDecl(lengthof(BuiltinTrancheNames) == diff --git a/src/include/storage/condition_variable.h b/src/include/storage/condition_variable.h index e89175ebd5..82b8eeabf5 100644 --- a/src/include/storage/condition_variable.h +++ b/src/include/storage/condition_variable.h @@ -22,12 +22,12 @@ #ifndef CONDITION_VARIABLE_H #define CONDITION_VARIABLE_H +#include "storage/lwlock.h" #include "storage/proclist_types.h" -#include "storage/spin.h" typedef struct { - slock_t mutex; /* spinlock protecting the wakeup list */ + LWLock mutex; /* lock protecting the wakeup list */ proclist_head wakeup; /* list of wake-able processes */ } ConditionVariable; diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index ca4eca76f4..dcbffe11a2 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -193,6 +193,7 @@ typedef enum BuiltinTrancheIds LWTRANCHE_PGSTATS_DSA, LWTRANCHE_PGSTATS_HASH, LWTRANCHE_PGSTATS_DATA, + LWTRANCHE_CONDITION_VARIABLE, LWTRANCHE_FIRST_USER_DEFINED } BuiltinTrancheIds; -- 2.35.1 [text/x-patch] 0003-Use-SetLatches-for-heavyweight-locks.patch (13.6K, ../../CA+hUKGKmO7ze0Z6WXKdrLxmvYa=zVGGXOO30MMktufofVwEm1A@mail.gmail.com/4-0003-Use-SetLatches-for-heavyweight-locks.patch) download | inline diff: From 1dca369e595f1259736a015efcf13b1041d7780d Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Wed, 26 Oct 2022 16:43:31 +1300 Subject: [PATCH 3/8] Use SetLatches() for heavyweight locks. Collect wakeups into a LatchBatch to be set at once after the LockManager's internal partition lock is released. This avoids holding busy locks while issuing system calls. Currently, waiters immediately try to acquire that lock themselves, so deferring until it's released makes sense to avoid contention (a later patch may remove that lock acquisition though). --- src/backend/storage/lmgr/deadlock.c | 4 ++-- src/backend/storage/lmgr/lock.c | 36 +++++++++++++++++++---------- src/backend/storage/lmgr/proc.c | 29 ++++++++++++++--------- src/include/storage/lock.h | 5 ++-- src/include/storage/proc.h | 5 ++-- 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/backend/storage/lmgr/deadlock.c b/src/backend/storage/lmgr/deadlock.c index cd9c0418ec..1b7454a8fe 100644 --- a/src/backend/storage/lmgr/deadlock.c +++ b/src/backend/storage/lmgr/deadlock.c @@ -214,7 +214,7 @@ InitDeadLockChecking(void) * and (b) we are typically invoked inside a signal handler. */ DeadLockState -DeadLockCheck(PGPROC *proc) +DeadLockCheck(PGPROC *proc, LatchBatch *wakeups) { int i, j; @@ -272,7 +272,7 @@ DeadLockCheck(PGPROC *proc) #endif /* See if any waiters for the lock can be woken up now */ - ProcLockWakeup(GetLocksMethodTable(lock), lock); + ProcLockWakeup(GetLocksMethodTable(lock), lock, wakeups); } /* Return code tells caller if we had to escape a deadlock or not */ diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 3d1049cf75..f59ae20069 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -374,14 +374,14 @@ static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc, static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner); static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode); static void FinishStrongLockAcquire(void); -static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner); +static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, LatchBatch *wakeups); static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock); static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent); static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode, PROCLOCK *proclock, LockMethod lockMethodTable); static void CleanUpLock(LOCK *lock, PROCLOCK *proclock, LockMethod lockMethodTable, uint32 hashcode, - bool wakeupNeeded); + bool wakeupNeeded, LatchBatch *wakeups); static void LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, LOCKTAG *locktag, LOCKMODE lockmode, bool decrement_strong_lock_count); @@ -787,6 +787,7 @@ LockAcquireExtended(const LOCKTAG *locktag, LWLock *partitionLock; bool found_conflict; bool log_lock = false; + LatchBatch wakeups = {0}; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) elog(ERROR, "unrecognized lock method: %d", lockmethodid); @@ -1098,7 +1099,7 @@ LockAcquireExtended(const LOCKTAG *locktag, locktag->locktag_type, lockmode); - WaitOnLock(locallock, owner); + WaitOnLock(locallock, owner, &wakeups); TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1, locktag->locktag_field2, @@ -1138,6 +1139,8 @@ LockAcquireExtended(const LOCKTAG *locktag, LWLockRelease(partitionLock); + SetLatches(&wakeups); + /* * Emit a WAL record if acquisition of this lock needs to be replayed in a * standby server. @@ -1634,7 +1637,7 @@ UnGrantLock(LOCK *lock, LOCKMODE lockmode, static void CleanUpLock(LOCK *lock, PROCLOCK *proclock, LockMethod lockMethodTable, uint32 hashcode, - bool wakeupNeeded) + bool wakeupNeeded, LatchBatch *wakeups) { /* * If this was my last hold on this lock, delete my entry in the proclock @@ -1674,7 +1677,7 @@ CleanUpLock(LOCK *lock, PROCLOCK *proclock, else if (wakeupNeeded) { /* There are waiters on this lock, so wake them up. */ - ProcLockWakeup(lockMethodTable, lock); + ProcLockWakeup(lockMethodTable, lock, wakeups); } } @@ -1811,7 +1814,7 @@ MarkLockClear(LOCALLOCK *locallock) * The appropriate partition lock must be held at entry. */ static void -WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner) +WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, LatchBatch *wakeups) { LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock); LockMethod lockMethodTable = LockMethods[lockmethodid]; @@ -1856,7 +1859,7 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner) */ PG_TRY(); { - if (ProcSleep(locallock, lockMethodTable) != PROC_WAIT_STATUS_OK) + if (ProcSleep(locallock, lockMethodTable, wakeups) != PROC_WAIT_STATUS_OK) { /* * We failed as a result of a deadlock, see CheckDeadLock(). Quit @@ -1915,7 +1918,7 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner) * NB: this does not clean up any locallock object that may exist for the lock. */ void -RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode) +RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode, LatchBatch *wakeups) { LOCK *waitLock = proc->waitLock; PROCLOCK *proclock = proc->waitProcLock; @@ -1957,7 +1960,7 @@ RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode) */ CleanUpLock(waitLock, proclock, LockMethods[lockmethodid], hashcode, - true); + true, wakeups); } /* @@ -1982,6 +1985,7 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) PROCLOCK *proclock; LWLock *partitionLock; bool wakeupNeeded; + LatchBatch wakeups = {0}; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) elog(ERROR, "unrecognized lock method: %d", lockmethodid); @@ -2160,10 +2164,12 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) CleanUpLock(lock, proclock, lockMethodTable, locallock->hashcode, - wakeupNeeded); + wakeupNeeded, &wakeups); LWLockRelease(partitionLock); + SetLatches(&wakeups); + RemoveLocalLock(locallock); return true; } @@ -2188,6 +2194,7 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) PROCLOCK *proclock; int partition; bool have_fast_path_lwlock = false; + LatchBatch wakeups = {0}; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) elog(ERROR, "unrecognized lock method: %d", lockmethodid); @@ -2434,10 +2441,12 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) CleanUpLock(lock, proclock, lockMethodTable, LockTagHashCode(&lock->tag), - wakeupNeeded); + wakeupNeeded, &wakeups); } /* loop over PROCLOCKs within this partition */ LWLockRelease(partitionLock); + + SetLatches(&wakeups); } /* loop over partitions */ #ifdef LOCK_DEBUG @@ -3137,6 +3146,7 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, uint32 proclock_hashcode; LWLock *partitionLock; bool wakeupNeeded; + LatchBatch wakeups = {0}; hashcode = LockTagHashCode(locktag); partitionLock = LockHashPartitionLock(hashcode); @@ -3190,10 +3200,12 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, CleanUpLock(lock, proclock, lockMethodTable, hashcode, - wakeupNeeded); + wakeupNeeded, &wakeups); LWLockRelease(partitionLock); + SetLatches(&wakeups); + /* * Decrement strong lock count. This logic is needed only for 2PC. */ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 13fa07b0ff..f0e19b74a7 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -700,6 +700,7 @@ LockErrorCleanup(void) { LWLock *partitionLock; DisableTimeoutParams timeouts[2]; + LatchBatch wakeups = {0}; HOLD_INTERRUPTS(); @@ -733,7 +734,7 @@ LockErrorCleanup(void) if (MyProc->links.next != NULL) { /* We could not have been granted the lock yet */ - RemoveFromWaitQueue(MyProc, lockAwaited->hashcode); + RemoveFromWaitQueue(MyProc, lockAwaited->hashcode, &wakeups); } else { @@ -750,6 +751,7 @@ LockErrorCleanup(void) lockAwaited = NULL; LWLockRelease(partitionLock); + SetLatches(&wakeups); RESUME_INTERRUPTS(); } @@ -1042,7 +1044,7 @@ ProcQueueInit(PROC_QUEUE *queue) * NOTES: The process queue is now a priority queue for locking. */ ProcWaitStatus -ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) +ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchBatch *wakeups) { LOCKMODE lockmode = locallock->tag.mode; LOCK *lock = locallock->lock; @@ -1188,7 +1190,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) */ if (early_deadlock) { - RemoveFromWaitQueue(MyProc, hashcode); + RemoveFromWaitQueue(MyProc, hashcode, wakeups); return PROC_WAIT_STATUS_ERROR; } @@ -1204,6 +1206,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * LockErrorCleanup will clean up if cancel/die happens. */ LWLockRelease(partitionLock); + SetLatches(wakeups); /* * Also, now that we will successfully clean up after an ereport, it's @@ -1662,8 +1665,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * to twiddle the lock's request counts too --- see RemoveFromWaitQueue. * Hence, in practice the waitStatus parameter must be PROC_WAIT_STATUS_OK. */ -PGPROC * -ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus) +static PGPROC * +ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus, LatchBatch *wakeups) { PGPROC *retProc; @@ -1686,8 +1689,8 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus) proc->waitStatus = waitStatus; pg_atomic_write_u64(&MyProc->waitStart, 0); - /* And awaken it */ - SetLatch(&proc->procLatch); + /* Schedule it to be awoken */ + AddLatch(wakeups, &proc->procLatch); return retProc; } @@ -1700,7 +1703,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus) * The appropriate lock partition lock must be held by caller. */ void -ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock) +ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock, LatchBatch *wakeups) { PROC_QUEUE *waitQueue = &(lock->waitProcs); int queue_size = waitQueue->size; @@ -1728,7 +1731,7 @@ ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock) { /* OK to waken */ GrantLock(lock, proc->waitProcLock, lockmode); - proc = ProcWakeup(proc, PROC_WAIT_STATUS_OK); + proc = ProcWakeup(proc, PROC_WAIT_STATUS_OK, wakeups); /* * ProcWakeup removes proc from the lock's waiting process queue @@ -1762,6 +1765,7 @@ static void CheckDeadLock(void) { int i; + LatchBatch wakeups = {0}; /* * Acquire exclusive lock on the entire shared lock data structures. Must @@ -1796,7 +1800,7 @@ CheckDeadLock(void) #endif /* Run the deadlock check, and set deadlock_state for use by ProcSleep */ - deadlock_state = DeadLockCheck(MyProc); + deadlock_state = DeadLockCheck(MyProc, &wakeups); if (deadlock_state == DS_HARD_DEADLOCK) { @@ -1813,7 +1817,8 @@ CheckDeadLock(void) * return from the signal handler. */ Assert(MyProc->waitLock != NULL); - RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag))); + RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)), + &wakeups); /* * We're done here. Transaction abort caused by the error that @@ -1837,6 +1842,8 @@ CheckDeadLock(void) check_done: for (i = NUM_LOCK_PARTITIONS; --i >= 0;) LWLockRelease(LockHashPartitionLockByIndex(i)); + + SetLatches(&wakeups); } /* diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index e4e1495b24..163bf41293 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -19,6 +19,7 @@ #endif #include "storage/backendid.h" +#include "storage/latch.h" #include "storage/lockdefs.h" #include "storage/lwlock.h" #include "storage/shmem.h" @@ -575,7 +576,7 @@ extern bool LockCheckConflicts(LockMethod lockMethodTable, LOCK *lock, PROCLOCK *proclock); extern void GrantLock(LOCK *lock, PROCLOCK *proclock, LOCKMODE lockmode); extern void GrantAwaitedLock(void); -extern void RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode); +extern void RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode, LatchBatch *wakeups); extern Size LockShmemSize(void); extern LockData *GetLockStatusData(void); extern BlockedProcsData *GetBlockerStatusData(int blocked_pid); @@ -592,7 +593,7 @@ extern void lock_twophase_postabort(TransactionId xid, uint16 info, extern void lock_twophase_standby_recover(TransactionId xid, uint16 info, void *recdata, uint32 len); -extern DeadLockState DeadLockCheck(PGPROC *proc); +extern DeadLockState DeadLockCheck(PGPROC *proc, LatchBatch *wakeups); extern PGPROC *GetBlockingAutoVacuumPgproc(void); extern void DeadLockReport(void) pg_attribute_noreturn(); extern void RememberSimpleDeadLock(PGPROC *proc1, diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 8d096fdeeb..4dba46cfcb 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -449,9 +449,8 @@ extern bool HaveNFreeProcs(int n); extern void ProcReleaseLocks(bool isCommit); extern void ProcQueueInit(PROC_QUEUE *queue); -extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable); -extern PGPROC *ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus); -extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock); +extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchBatch *wakeups); +extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock, LatchBatch *wakeups); extern void CheckDeadLockAlert(void); extern bool IsWaitingForLock(void); extern void LockErrorCleanup(void); -- 2.35.1 [text/x-patch] 0004-Don-t-re-acquire-LockManager-partition-lock-after-wa.patch (6.2K, ../../CA+hUKGKmO7ze0Z6WXKdrLxmvYa=zVGGXOO30MMktufofVwEm1A@mail.gmail.com/5-0004-Don-t-re-acquire-LockManager-partition-lock-after-wa.patch) download | inline diff: From 2cef9eb9236a19845f967de3f392124016bbbba9 Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Thu, 27 Oct 2022 10:56:39 +1300 Subject: [PATCH 4/8] Don't re-acquire LockManager partition lock after wakeup. Change the contract of WaitOnLock() and ProcSleep() to return with the partition lock released. ProcSleep() re-acquired the lock to prevent interrupts, which was a problem at the time the ancestor of that code was committed in fe548629c50, because then we had signal handlers that longjmp'd out of there. Now, that can happen only at points where we explicitly run CHECK_FOR_INTERRUPTS(), so we can remove the lock, which leads to the idea of making the function always release. While an earlier commit fixed the problem that the backend woke us up before it had even released the lock itself, this lock reacquisition point was still contended when multiple backends woke at the same time. XXX Right? Or what am I missing? --- src/backend/storage/lmgr/lock.c | 18 ++++++++++++------ src/backend/storage/lmgr/proc.c | 20 ++++++++++++-------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index f59ae20069..42c244f5fb 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -787,6 +787,7 @@ LockAcquireExtended(const LOCKTAG *locktag, LWLock *partitionLock; bool found_conflict; bool log_lock = false; + bool partitionLocked = false; LatchBatch wakeups = {0}; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) @@ -995,6 +996,7 @@ LockAcquireExtended(const LOCKTAG *locktag, partitionLock = LockHashPartitionLock(hashcode); LWLockAcquire(partitionLock, LW_EXCLUSIVE); + partitionLocked = true; /* * Find or create lock and proclock entries with this tag @@ -1099,7 +1101,10 @@ LockAcquireExtended(const LOCKTAG *locktag, locktag->locktag_type, lockmode); + Assert(LWLockHeldByMeInMode(partitionLock, LW_EXCLUSIVE)); WaitOnLock(locallock, owner, &wakeups); + Assert(!LWLockHeldByMeInMode(partitionLock, LW_EXCLUSIVE)); + partitionLocked = false; TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1, locktag->locktag_field2, @@ -1124,7 +1129,6 @@ LockAcquireExtended(const LOCKTAG *locktag, PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock); LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode); /* Should we retry ? */ - LWLockRelease(partitionLock); elog(ERROR, "LockAcquire failed"); } PROCLOCK_PRINT("LockAcquire: granted", proclock); @@ -1137,9 +1141,11 @@ LockAcquireExtended(const LOCKTAG *locktag, */ FinishStrongLockAcquire(); - LWLockRelease(partitionLock); - - SetLatches(&wakeups); + if (partitionLocked) + { + LWLockRelease(partitionLock); + SetLatches(&wakeups); + } /* * Emit a WAL record if acquisition of this lock needs to be replayed in a @@ -1811,7 +1817,8 @@ MarkLockClear(LOCALLOCK *locallock) * Caller must have set MyProc->heldLocks to reflect locks already held * on the lockable object by this process. * - * The appropriate partition lock must be held at entry. + * The appropriate partition lock must be held at entry. It is not held on + * exit. */ static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, LatchBatch *wakeups) @@ -1868,7 +1875,6 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, LatchBatch *wakeups) awaitedLock = NULL; LOCK_PRINT("WaitOnLock: aborting on lock", locallock->lock, locallock->tag.mode); - LWLockRelease(LockHashPartitionLock(locallock->hashcode)); /* * Now that we aren't holding the partition lock, we can give an diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index f0e19b74a7..2a03cd4b1f 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1033,7 +1033,7 @@ ProcQueueInit(PROC_QUEUE *queue) * Caller must have set MyProc->heldLocks to reflect locks already held * on the lockable object by this process (under all XIDs). * - * The lock table's partition lock must be held at entry, and will be held + * The lock table's partition lock must be held at entry, and will be released * at exit. * * Result: PROC_WAIT_STATUS_OK if we acquired the lock, PROC_WAIT_STATUS_ERROR if not (deadlock). @@ -1062,6 +1062,12 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchBatch *wakeups) PGPROC *leader = MyProc->lockGroupLeader; int i; + /* + * Every way out of this function will release the partition lock and send + * buffered latch wakeups. + */ + Assert(LWLockHeldByMeInMode(partitionLock, LW_EXCLUSIVE)); + /* * If group locking is in use, locks held by members of my locking group * need to be included in myHeldLocks. This is not required for relation @@ -1148,6 +1154,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchBatch *wakeups) /* Skip the wait and just grant myself the lock. */ GrantLock(lock, proclock, lockmode); GrantAwaitedLock(); + LWLockRelease(partitionLock); + SetLatches(wakeups); return PROC_WAIT_STATUS_OK; } /* Break out of loop to put myself before him */ @@ -1191,6 +1199,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchBatch *wakeups) if (early_deadlock) { RemoveFromWaitQueue(MyProc, hashcode, wakeups); + LWLockRelease(partitionLock); + SetLatches(wakeups); return PROC_WAIT_STATUS_ERROR; } @@ -1525,6 +1535,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchBatch *wakeups) } LWLockRelease(partitionLock); + SetLatches(wakeups); if (deadlock_state == DS_SOFT_DEADLOCK) ereport(LOG, @@ -1626,13 +1637,6 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchBatch *wakeups) standbyWaitStart, GetCurrentTimestamp(), NULL, false); - /* - * Re-acquire the lock table's partition lock. We have to do this to hold - * off cancel/die interrupts before we can mess with lockAwaited (else we - * might have a missed or duplicated locallock update). - */ - LWLockAcquire(partitionLock, LW_EXCLUSIVE); - /* * We no longer want LockErrorCleanup to do anything. */ -- 2.35.1 [text/x-patch] 0005-Use-SetLatches-for-SERIALIZABLE-DEFERRABLE-wakeups.patch (4.5K, ../../CA+hUKGKmO7ze0Z6WXKdrLxmvYa=zVGGXOO30MMktufofVwEm1A@mail.gmail.com/6-0005-Use-SetLatches-for-SERIALIZABLE-DEFERRABLE-wakeups.patch) download | inline diff: From e1ab9a2f56127c3731c0d5e0aa4fd0fc8243cdfa Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Thu, 27 Oct 2022 12:40:12 +1300 Subject: [PATCH 5/8] Use SetLatches() for SERIALIZABLE DEFERRABLE wakeups. Don't issue SetLatch()'s system call while holding a highly contended LWLock. Collect them in a LatchBatch to be set after the lock is released. Once woken, other backends will immediately try to acquire that lock anyway so it's better to wake after releasing. Take this opportunity to retire the confusingly named ProcSendSignal() and replace it with something that gives the latch pointer we need. There was only one other caller, in bufmgr.c, which is easily changed. --- src/backend/storage/buffer/bufmgr.c | 2 +- src/backend/storage/ipc/procsignal.c | 2 -- src/backend/storage/lmgr/predicate.c | 5 ++++- src/backend/storage/lmgr/proc.c | 12 ------------ src/include/storage/proc.h | 4 +++- 5 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 6b95381481..4d754caa30 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -1918,7 +1918,7 @@ UnpinBuffer(BufferDesc *buf) buf_state &= ~BM_PIN_COUNT_WAITER; UnlockBufHdr(buf, buf_state); - ProcSendSignal(wait_backend_pgprocno); + SetLatch(GetProcLatchByNumber(wait_backend_pgprocno)); } else UnlockBufHdr(buf, buf_state); diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index 7767657f27..280e4b91dd 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -254,8 +254,6 @@ CleanupProcSignalState(int status, Datum arg) * * On success (a signal was sent), zero is returned. * On error, -1 is returned, and errno is set (typically to ESRCH or EPERM). - * - * Not to be confused with ProcSendSignal */ int SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId) diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index e8120174d6..a9e9fa01e7 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -3339,6 +3339,7 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe) nextConflict, possibleUnsafeConflict; SERIALIZABLEXACT *roXact; + LatchBatch wakeups = {0}; /* * We can't trust XactReadOnly here, because a transaction which started @@ -3672,7 +3673,7 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe) */ if (SxactIsDeferrableWaiting(roXact) && (SxactIsROUnsafe(roXact) || SxactIsROSafe(roXact))) - ProcSendSignal(roXact->pgprocno); + AddLatch(&wakeups, GetProcLatchByNumber(roXact->pgprocno)); possibleUnsafeConflict = nextConflict; } @@ -3698,6 +3699,8 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe) LWLockRelease(SerializableXactHashLock); + SetLatches(&wakeups); + LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE); /* Add this to the list of transactions to check for later cleanup. */ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 2a03cd4b1f..c644e7351d 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1890,18 +1890,6 @@ ProcWaitForSignal(uint32 wait_event_info) CHECK_FOR_INTERRUPTS(); } -/* - * ProcSendSignal - set the latch of a backend identified by pgprocno - */ -void -ProcSendSignal(int pgprocno) -{ - if (pgprocno < 0 || pgprocno >= ProcGlobal->allProcCount) - elog(ERROR, "pgprocno out of range"); - - SetLatch(&ProcGlobal->allProcs[pgprocno].procLatch); -} - /* * BecomeLockGroupLeader - designate process as lock group leader * diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 4dba46cfcb..735e863ed1 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -413,6 +413,9 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; /* Accessor for PGPROC given a pgprocno. */ #define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)]) +/* Accessor for procLatch given a pgprocno. */ +#define GetProcLatchByNumber(n) (&(GetPGProcByNumber(n))->procLatch) + /* * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. @@ -456,7 +459,6 @@ extern bool IsWaitingForLock(void); extern void LockErrorCleanup(void); extern void ProcWaitForSignal(uint32 wait_event_info); -extern void ProcSendSignal(int pgprocno); extern PGPROC *AuxiliaryPidGetProc(int pid); -- 2.35.1 [text/x-patch] 0006-Use-SetLatches-for-synchronous-replication-wakeups.patch (3.3K, ../../CA+hUKGKmO7ze0Z6WXKdrLxmvYa=zVGGXOO30MMktufofVwEm1A@mail.gmail.com/7-0006-Use-SetLatches-for-synchronous-replication-wakeups.patch) download | inline diff: From 351ce81b4d1b2a61fcfcc9902d7122e5410c8b63 Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Thu, 27 Oct 2022 12:59:45 +1300 Subject: [PATCH 6/8] Use SetLatches() for synchronous replication wakeups. Don't issue SetLatch() system calls while holding the central synchronous replication lock. --- src/backend/replication/syncrep.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index 1a022b11a0..31cf05ed2f 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -100,7 +100,7 @@ static int SyncRepWaitMode = SYNC_REP_NO_WAIT; static void SyncRepQueueInsert(int mode); static void SyncRepCancelWait(void); -static int SyncRepWakeQueue(bool all, int mode); +static int SyncRepWakeQueue(bool all, int mode, LatchBatch *wakeups); static bool SyncRepGetSyncRecPtr(XLogRecPtr *writePtr, XLogRecPtr *flushPtr, @@ -449,6 +449,7 @@ SyncRepReleaseWaiters(void) int numwrite = 0; int numflush = 0; int numapply = 0; + LatchBatch wakeups = {0}; /* * If this WALSender is serving a standby that is not on the list of @@ -518,21 +519,23 @@ SyncRepReleaseWaiters(void) if (walsndctl->lsn[SYNC_REP_WAIT_WRITE] < writePtr) { walsndctl->lsn[SYNC_REP_WAIT_WRITE] = writePtr; - numwrite = SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE); + numwrite = SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE, &wakeups); } if (walsndctl->lsn[SYNC_REP_WAIT_FLUSH] < flushPtr) { walsndctl->lsn[SYNC_REP_WAIT_FLUSH] = flushPtr; - numflush = SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH); + numflush = SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH, &wakeups); } if (walsndctl->lsn[SYNC_REP_WAIT_APPLY] < applyPtr) { walsndctl->lsn[SYNC_REP_WAIT_APPLY] = applyPtr; - numapply = SyncRepWakeQueue(false, SYNC_REP_WAIT_APPLY); + numapply = SyncRepWakeQueue(false, SYNC_REP_WAIT_APPLY, &wakeups); } LWLockRelease(SyncRepLock); + SetLatches(&wakeups); + elog(DEBUG3, "released %d procs up to write %X/%X, %d procs up to flush %X/%X, %d procs up to apply %X/%X", numwrite, LSN_FORMAT_ARGS(writePtr), numflush, LSN_FORMAT_ARGS(flushPtr), @@ -876,7 +879,7 @@ SyncRepGetStandbyPriority(void) * The caller must hold SyncRepLock in exclusive mode. */ static int -SyncRepWakeQueue(bool all, int mode) +SyncRepWakeQueue(bool all, int mode, LatchBatch *wakeups) { volatile WalSndCtlData *walsndctl = WalSndCtl; PGPROC *proc = NULL; @@ -929,7 +932,7 @@ SyncRepWakeQueue(bool all, int mode) /* * Wake only when we have set state and removed from queue. */ - SetLatch(&(thisproc->procLatch)); + AddLatch(wakeups, &thisproc->procLatch); numprocs++; } @@ -951,6 +954,8 @@ SyncRepUpdateSyncStandbysDefined(void) if (sync_standbys_defined != WalSndCtl->sync_standbys_defined) { + LatchBatch wakeups = {0}; + LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); /* @@ -963,7 +968,7 @@ SyncRepUpdateSyncStandbysDefined(void) int i; for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++) - SyncRepWakeQueue(true, i); + SyncRepWakeQueue(true, i, &wakeups); } /* @@ -976,6 +981,8 @@ SyncRepUpdateSyncStandbysDefined(void) WalSndCtl->sync_standbys_defined = sync_standbys_defined; LWLockRelease(SyncRepLock); + + SetLatches(&wakeups); } } -- 2.35.1 ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Latches vs lwlock contention @ 2022-11-01 11:09 Thomas Munro <[email protected]> parent: Thomas Munro <[email protected]> 1 sibling, 0 replies; 7+ messages in thread From: Thomas Munro @ 2022-11-01 11:09 UTC (permalink / raw) To: pgsql-hackers; +Cc: Yura Sokolov <[email protected]>; Andres Freund <[email protected]> On Fri, Oct 28, 2022 at 4:56 PM Thomas Munro <[email protected]> wrote: > See attached sketch patches. I guess the main thing that may not be > good enough is the use of a fixed sized latch buffer. Memory > allocation in don't-throw-here environments like the guts of lock code > might be an issue, which is why it just gives up and flushes when > full; maybe it should try to allocate and fall back to flushing only > if that fails. Here's an attempt at that. There aren't actually any cases of uses of this stuff in critical sections here, so perhaps I shouldn't bother with that part. The part I'd most like some feedback on is the heavyweight lock bits. I'll add this to the commitfest. Attachments: [application/octet-stream] v2-0001-Allow-palloc_extended-NO_OOM-in-critical-sections.patch (1.9K, ../../CA+hUKG+UfnpJwxUJO-XoAJ-JabXAhmkTwwjG8HLfi7XiQz_bxA@mail.gmail.com/2-v2-0001-Allow-palloc_extended-NO_OOM-in-critical-sections.patch) download | inline diff: From 383e2d9b0d24e02ca468b32f8bd3775bbecf1e86 Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Tue, 1 Nov 2022 23:29:38 +1300 Subject: [PATCH v2 1/9] Allow palloc_extended(NO_OOM) in critical sections. Commit 4a170ee9e0e banned palloc() and similar in critical sections, because an allocation failure would produce a panic. Make an exception for allocation with NULL on failure, for code that has a backup plan. Discussion: https://postgr.es/m/CA%2BhUKGJHudZMG_vh6GiPB61pE%2BGgiBk5jxzd7inijqx5nEZLCw%40mail.gmail.com --- src/backend/utils/mmgr/mcxt.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index f526ca82c1..460fa9d6c0 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -1112,7 +1112,8 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags) void *ret; Assert(MemoryContextIsValid(context)); - AssertNotInCriticalSection(context); + if ((flags & MCXT_ALLOC_NO_OOM) == 0) + AssertNotInCriticalSection(context); if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) : AllocSizeIsValid(size))) @@ -1267,7 +1268,8 @@ palloc_extended(Size size, int flags) MemoryContext context = CurrentMemoryContext; Assert(MemoryContextIsValid(context)); - AssertNotInCriticalSection(context); + if ((flags & MCXT_ALLOC_NO_OOM) == 0) + AssertNotInCriticalSection(context); if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) : AllocSizeIsValid(size))) @@ -1368,7 +1370,8 @@ repalloc_extended(void *pointer, Size size, int flags) AllocSizeIsValid(size))) elog(ERROR, "invalid memory alloc request size %zu", size); - AssertNotInCriticalSection(context); + if ((flags & MCXT_ALLOC_NO_OOM) == 0) + AssertNotInCriticalSection(context); /* isReset must be false already */ Assert(!context->isReset); -- 2.37.0 (Apple Git-136) [application/octet-stream] v2-0002-Provide-SetLatches-for-batched-deferred-latches.patch (9.3K, ../../CA+hUKG+UfnpJwxUJO-XoAJ-JabXAhmkTwwjG8HLfi7XiQz_bxA@mail.gmail.com/3-v2-0002-Provide-SetLatches-for-batched-deferred-latches.patch) download | inline diff: From e30aab2276b05212dbfe23d2a28636036610645d Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Wed, 26 Oct 2022 15:51:45 +1300 Subject: [PATCH v2 2/9] Provide SetLatches() for batched deferred latches. If we have a way to buffer a set of wakeup targets and process them at a later time, we can: * move SetLatch() system calls out from under LWLocks, so that locks can be released faster; this is especially interesting in cases where the target backends will immediately try to acquire the same lock, or generally when the lock is heavily contended * possibly gain some micro-opimization from issuing only two memory barriers for the whole batch of latches, not two for each latch to be set * prepare for future IPC mechanisms which might allow us to wake multiple backends in a single system call Individual users of this facility will follow in separate patches. Discussion: https://postgr.es/m/CA%2BhUKGKmO7ze0Z6WXKdrLxmvYa%3DzVGGXOO30MMktufofVwEm1A%40mail.gmail.com --- src/backend/storage/ipc/latch.c | 223 +++++++++++++++++++++---------- src/include/storage/latch.h | 13 ++ src/tools/pgindent/typedefs.list | 1 + 3 files changed, 163 insertions(+), 74 deletions(-) diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c index eb3a569aae..199ccc1e65 100644 --- a/src/backend/storage/ipc/latch.c +++ b/src/backend/storage/ipc/latch.c @@ -575,6 +575,94 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock, return ret; } +/* + * Set multiple latches at the same time. + * Note: modifies input array. + */ +static void +SetLatchV(Latch **latches, int nlatches) +{ + /* Flush any other changes out to main memory just once. */ + pg_memory_barrier(); + + /* Keep only latches that are not already set, and set them. */ + for (int i = 0; i < nlatches; ++i) + { + Latch *latch = latches[i]; + + if (!latch->is_set) + latch->is_set = true; + else + latches[i] = NULL; + } + + pg_memory_barrier(); + + /* Wake the remaining latches that might be sleeping. */ +#ifndef WIN32 + for (int i = 0; i < nlatches; ++i) + { + Latch *latch = latches[i]; + pid_t owner_pid; + + if (!latch || !latch->maybe_sleeping) + continue; + + /* + * See if anyone's waiting for the latch. It can be the current + * process if we're in a signal handler. We use the self-pipe or + * SIGURG to ourselves to wake up WaitEventSetWaitBlock() without + * races in that case. If it's another process, send a signal. + * + * Fetch owner_pid only once, in case the latch is concurrently + * getting owned or disowned. XXX: This assumes that pid_t is atomic, + * which isn't guaranteed to be true! In practice, the effective range + * of pid_t fits in a 32 bit integer, and so should be atomic. In the + * worst case, we might end up signaling the wrong process. Even then, + * you're very unlucky if a process with that bogus pid exists and + * belongs to Postgres; and PG database processes should handle excess + * SIGURG interrupts without a problem anyhow. + * + * Another sort of race condition that's possible here is for a new + * process to own the latch immediately after we look, so we don't + * signal it. This is okay so long as all callers of + * ResetLatch/WaitLatch follow the standard coding convention of + * waiting at the bottom of their loops, not the top, so that they'll + * correctly process latch-setting events that happen before they + * enter the loop. + */ + owner_pid = latch->owner_pid; + + if (owner_pid == MyProcPid) + { + if (waiting) + { +#if defined(WAIT_USE_SELF_PIPE) + sendSelfPipeByte(); +#else + kill(MyProcPid, SIGURG); +#endif + } + } + else + kill(owner_pid, SIGURG); + } +#else + for (int i = 0; i < nlatches; ++i) + { + Latch *latch = latches[i]; + + if (latch && latch->maybe_sleeping) + { + HANDLE event = latch->event; + + if (event) + SetEvent(event); + } + } +#endif +} + /* * Sets a latch and wakes up anyone waiting on it. * @@ -590,89 +678,76 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock, void SetLatch(Latch *latch) { -#ifndef WIN32 - pid_t owner_pid; -#else - HANDLE handle; -#endif - - /* - * The memory barrier has to be placed here to ensure that any flag - * variables possibly changed by this process have been flushed to main - * memory, before we check/set is_set. - */ - pg_memory_barrier(); - - /* Quick exit if already set */ - if (latch->is_set) - return; - - latch->is_set = true; - - pg_memory_barrier(); - if (!latch->maybe_sleeping) - return; + SetLatchV(&latch, 1); +} -#ifndef WIN32 +/* + * Add a latch to a group, to be set later. + */ +void +AddLatch(LatchGroup *group, Latch *latch) +{ + /* First time. Set up the in-place buffer. */ + if (!group->latches) + { + group->latches = group->in_place_buffer; + group->capacity = lengthof(group->in_place_buffer); + Assert(group->size == 0); + } - /* - * See if anyone's waiting for the latch. It can be the current process if - * we're in a signal handler. We use the self-pipe or SIGURG to ourselves - * to wake up WaitEventSetWaitBlock() without races in that case. If it's - * another process, send a signal. - * - * Fetch owner_pid only once, in case the latch is concurrently getting - * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't - * guaranteed to be true! In practice, the effective range of pid_t fits - * in a 32 bit integer, and so should be atomic. In the worst case, we - * might end up signaling the wrong process. Even then, you're very - * unlucky if a process with that bogus pid exists and belongs to - * Postgres; and PG database processes should handle excess SIGUSR1 - * interrupts without a problem anyhow. - * - * Another sort of race condition that's possible here is for a new - * process to own the latch immediately after we look, so we don't signal - * it. This is okay so long as all callers of ResetLatch/WaitLatch follow - * the standard coding convention of waiting at the bottom of their loops, - * not the top, so that they'll correctly process latch-setting events - * that happen before they enter the loop. - */ - owner_pid = latch->owner_pid; - if (owner_pid == 0) - return; - else if (owner_pid == MyProcPid) + /* Are we full? */ + if (group->size == group->capacity) { -#if defined(WAIT_USE_SELF_PIPE) - if (waiting) - sendSelfPipeByte(); -#else - if (waiting) - kill(MyProcPid, SIGURG); -#endif + Latch **new_latches; + int new_capacity; + + /* Try to allocate more space. */ + new_capacity = group->capacity * 2; + new_latches = palloc_extended(sizeof(latch) * new_capacity, + MCXT_ALLOC_NO_OOM); + if (!new_latches) + { + /* + * Allocation failed. This is very unlikely to happen, but it + * might be useful to be able to use this function in critical + * sections, so we handle allocation failure by flushing instead + * of throwing. + */ + SetLatches(group); + } + else + { + memcpy(new_latches, group->latches, sizeof(latch) * group->size); + if (group->latches != group->in_place_buffer) + pfree(group->latches); + group->latches = new_latches; + group->capacity = new_capacity; + } } - else - kill(owner_pid, SIGURG); -#else + Assert(group->size < group->capacity); + group->latches[group->size++] = latch; +} - /* - * See if anyone's waiting for the latch. It can be the current process if - * we're in a signal handler. - * - * Use a local variable here just in case somebody changes the event field - * concurrently (which really should not happen). - */ - handle = latch->event; - if (handle) +/* + * Set all the latches accumulated in 'group'. + */ +void +SetLatches(LatchGroup *group) +{ + if (group->size > 0) { - SetEvent(handle); + SetLatchV(group->latches, group->size); + group->size = 0; - /* - * Note that we silently ignore any errors. We might be in a signal - * handler or other critical path where it's not safe to call elog(). - */ + /* If we allocated a large buffer, it's time to free it. */ + if (group->latches != group->in_place_buffer) + { + pfree(group->latches); + group->latches = group->in_place_buffer; + group->capacity = lengthof(group->in_place_buffer); + } } -#endif } /* diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h index 68ab740f16..09c2b338c6 100644 --- a/src/include/storage/latch.h +++ b/src/include/storage/latch.h @@ -118,6 +118,17 @@ typedef struct Latch #endif } Latch; +/* + * A buffer for setting multiple latches at a time. + */ +typedef struct LatchGroup +{ + int size; + int capacity; + Latch **latches; + Latch *in_place_buffer[64]; +} LatchGroup; + /* * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or * WaitEventSetWait(). @@ -163,6 +174,8 @@ extern void InitSharedLatch(Latch *latch); extern void OwnLatch(Latch *latch); extern void DisownLatch(Latch *latch); extern void SetLatch(Latch *latch); +extern void AddLatch(LatchGroup *group, Latch *latch); +extern void SetLatches(LatchGroup *group); extern void ResetLatch(Latch *latch); extern void ShutdownLatchSupport(void); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 2f02cc8f42..c86e49c758 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1389,6 +1389,7 @@ LagTracker LargeObjectDesc LastAttnumInfo Latch +LatchGroup LerpFunc LexDescr LexemeEntry -- 2.37.0 (Apple Git-136) [application/octet-stream] v2-0003-Use-SetLatches-for-condition-variables.patch (7.9K, ../../CA+hUKG+UfnpJwxUJO-XoAJ-JabXAhmkTwwjG8HLfi7XiQz_bxA@mail.gmail.com/4-v2-0003-Use-SetLatches-for-condition-variables.patch) download | inline diff: From cc97393e11ed3027ae5b78623a2903e70134aade Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Wed, 26 Oct 2022 17:36:34 +1300 Subject: [PATCH v2 3/9] Use SetLatches() for condition variables. Drain condition variable wait lists in larger batches, not one at a time, while broadcasting. This is an idea from Yura Sokolov, which I've now combined with SetLatches() for slightly more efficiency. Since we're now performing loops, change the internal spinlock to an lwlock. XXX There is probably a better data structure/arrangement that would allow us to hold the lock for a shorter time. This patch is not very satisfying yet. Discussion: https://postgr.es/m/CA%2BhUKGKmO7ze0Z6WXKdrLxmvYa%3DzVGGXOO30MMktufofVwEm1A%40mail.gmail.com Discussion: https://postgr.es/m/1edbb61981fe1d99c3f20e3d56d6c88999f4227c.camel%40postgrespro.ru --- src/backend/storage/lmgr/condition_variable.c | 96 +++++++++---------- src/backend/storage/lmgr/lwlock.c | 2 + src/include/storage/condition_variable.h | 4 +- src/include/storage/lwlock.h | 1 + 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c index de65dac3ae..b975a91172 100644 --- a/src/backend/storage/lmgr/condition_variable.c +++ b/src/backend/storage/lmgr/condition_variable.c @@ -36,7 +36,7 @@ static ConditionVariable *cv_sleep_target = NULL; void ConditionVariableInit(ConditionVariable *cv) { - SpinLockInit(&cv->mutex); + LWLockInitialize(&cv->mutex, LWTRANCHE_CONDITION_VARIABLE); proclist_init(&cv->wakeup); } @@ -74,9 +74,9 @@ ConditionVariablePrepareToSleep(ConditionVariable *cv) cv_sleep_target = cv; /* Add myself to the wait queue. */ - SpinLockAcquire(&cv->mutex); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink); - SpinLockRelease(&cv->mutex); + LWLockRelease(&cv->mutex); } /* @@ -180,13 +180,13 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout, * by something other than ConditionVariableSignal; though we don't * guarantee not to return spuriously, we'll avoid this obvious case. */ - SpinLockAcquire(&cv->mutex); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); if (!proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink)) { done = true; proclist_push_tail(&cv->wakeup, MyProc->pgprocno, cvWaitLink); } - SpinLockRelease(&cv->mutex); + LWLockRelease(&cv->mutex); /* * Check for interrupts, and return spuriously if that caused the @@ -233,12 +233,12 @@ ConditionVariableCancelSleep(void) if (cv == NULL) return; - SpinLockAcquire(&cv->mutex); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); if (proclist_contains(&cv->wakeup, MyProc->pgprocno, cvWaitLink)) proclist_delete(&cv->wakeup, MyProc->pgprocno, cvWaitLink); else signaled = true; - SpinLockRelease(&cv->mutex); + LWLockRelease(&cv->mutex); /* * If we've received a signal, pass it on to another waiting process, if @@ -265,10 +265,10 @@ ConditionVariableSignal(ConditionVariable *cv) PGPROC *proc = NULL; /* Remove the first process from the wakeup queue (if any). */ - SpinLockAcquire(&cv->mutex); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); if (!proclist_is_empty(&cv->wakeup)) proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink); - SpinLockRelease(&cv->mutex); + LWLockRelease(&cv->mutex); /* If we found someone sleeping, set their latch to wake them up. */ if (proc != NULL) @@ -287,6 +287,7 @@ ConditionVariableBroadcast(ConditionVariable *cv) { int pgprocno = MyProc->pgprocno; PGPROC *proc = NULL; + bool inserted_sentinel = false; bool have_sentinel = false; /* @@ -313,52 +314,43 @@ ConditionVariableBroadcast(ConditionVariable *cv) if (cv_sleep_target != NULL) ConditionVariableCancelSleep(); - /* - * Inspect the state of the queue. If it's empty, we have nothing to do. - * If there's exactly one entry, we need only remove and signal that - * entry. Otherwise, remove the first entry and insert our sentinel. - */ - SpinLockAcquire(&cv->mutex); - /* While we're here, let's assert we're not in the list. */ - Assert(!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink)); - - if (!proclist_is_empty(&cv->wakeup)) + do { - proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink); - if (!proclist_is_empty(&cv->wakeup)) - { - proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink); - have_sentinel = true; - } - } - SpinLockRelease(&cv->mutex); + LatchGroup wakeups = {0}; - /* Awaken first waiter, if there was one. */ - if (proc != NULL) - SetLatch(&proc->procLatch); + LWLockAcquire(&cv->mutex, LW_EXCLUSIVE); + while (!proclist_is_empty(&cv->wakeup)) + { + /* Wake up in small sized batches. XXX tune */ + if (wakeups.size == 8) + { + if (!inserted_sentinel) + { + /* First batch of many to wake up. Add sentinel. */ + proclist_push_tail(&cv->wakeup, pgprocno, cvWaitLink); + inserted_sentinel = true; + have_sentinel = true; + } + break; + } - while (have_sentinel) - { - /* - * Each time through the loop, remove the first wakeup list entry, and - * signal it unless it's our sentinel. Repeat as long as the sentinel - * remains in the list. - * - * Notice that if someone else removes our sentinel, we will waken one - * additional process before exiting. That's intentional, because if - * someone else signals the CV, they may be intending to waken some - * third process that added itself to the list after we added the - * sentinel. Better to give a spurious wakeup (which should be - * harmless beyond wasting some cycles) than to lose a wakeup. - */ - proc = NULL; - SpinLockAcquire(&cv->mutex); - if (!proclist_is_empty(&cv->wakeup)) proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink); - have_sentinel = proclist_contains(&cv->wakeup, pgprocno, cvWaitLink); - SpinLockRelease(&cv->mutex); + if (proc == MyProc) + { + /* We hit our sentinel. We're done. */ + have_sentinel = false; + break; + } + else if (!proclist_contains(&cv->wakeup, pgprocno, cvWaitLink)) + { + /* Someone else hit our sentinel. We're done. */ + have_sentinel = false; + } + AddLatch(&wakeups, &proc->procLatch); + } + LWLockRelease(&cv->mutex); - if (proc != NULL && proc != MyProc) - SetLatch(&proc->procLatch); - } + /* Awaken this batch of waiters, if there were some. */ + SetLatches(&wakeups); + } while (have_sentinel); } diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index 0fc0cf6ebb..77986bac7e 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -183,6 +183,8 @@ static const char *const BuiltinTrancheNames[] = { "PgStatsHash", /* LWTRANCHE_PGSTATS_DATA: */ "PgStatsData", + /* LWTRANCHE_CONDITION_VARIABLE: */ + "ConditionVariable", }; StaticAssertDecl(lengthof(BuiltinTrancheNames) == diff --git a/src/include/storage/condition_variable.h b/src/include/storage/condition_variable.h index e89175ebd5..82b8eeabf5 100644 --- a/src/include/storage/condition_variable.h +++ b/src/include/storage/condition_variable.h @@ -22,12 +22,12 @@ #ifndef CONDITION_VARIABLE_H #define CONDITION_VARIABLE_H +#include "storage/lwlock.h" #include "storage/proclist_types.h" -#include "storage/spin.h" typedef struct { - slock_t mutex; /* spinlock protecting the wakeup list */ + LWLock mutex; /* lock protecting the wakeup list */ proclist_head wakeup; /* list of wake-able processes */ } ConditionVariable; diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index ca4eca76f4..dcbffe11a2 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -193,6 +193,7 @@ typedef enum BuiltinTrancheIds LWTRANCHE_PGSTATS_DSA, LWTRANCHE_PGSTATS_HASH, LWTRANCHE_PGSTATS_DATA, + LWTRANCHE_CONDITION_VARIABLE, LWTRANCHE_FIRST_USER_DEFINED } BuiltinTrancheIds; -- 2.37.0 (Apple Git-136) [application/octet-stream] v2-0004-Use-SetLatches-for-heavyweight-locks.patch (13.8K, ../../CA+hUKG+UfnpJwxUJO-XoAJ-JabXAhmkTwwjG8HLfi7XiQz_bxA@mail.gmail.com/5-v2-0004-Use-SetLatches-for-heavyweight-locks.patch) download | inline diff: From fda454d2246ef050465f48ba18c0144c25dd2834 Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Wed, 26 Oct 2022 16:43:31 +1300 Subject: [PATCH v2 4/9] Use SetLatches() for heavyweight locks. Collect wakeups into a LatchGroup to be set at once after the LockManager's internal partition lock is released. This avoids holding busy locks while issuing system calls. Currently, waiters immediately try to acquire that lock themselves, so deferring until it's released makes sense to avoid contention (a later patch may remove that lock acquisition though). Discussion: https://postgr.es/m/CA%2BhUKGKmO7ze0Z6WXKdrLxmvYa%3DzVGGXOO30MMktufofVwEm1A%40mail.gmail.com --- src/backend/storage/lmgr/deadlock.c | 4 ++-- src/backend/storage/lmgr/lock.c | 36 +++++++++++++++++++---------- src/backend/storage/lmgr/proc.c | 29 ++++++++++++++--------- src/include/storage/lock.h | 5 ++-- src/include/storage/proc.h | 5 ++-- 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/backend/storage/lmgr/deadlock.c b/src/backend/storage/lmgr/deadlock.c index cd9c0418ec..2876be16f3 100644 --- a/src/backend/storage/lmgr/deadlock.c +++ b/src/backend/storage/lmgr/deadlock.c @@ -214,7 +214,7 @@ InitDeadLockChecking(void) * and (b) we are typically invoked inside a signal handler. */ DeadLockState -DeadLockCheck(PGPROC *proc) +DeadLockCheck(PGPROC *proc, LatchGroup *wakeups) { int i, j; @@ -272,7 +272,7 @@ DeadLockCheck(PGPROC *proc) #endif /* See if any waiters for the lock can be woken up now */ - ProcLockWakeup(GetLocksMethodTable(lock), lock); + ProcLockWakeup(GetLocksMethodTable(lock), lock, wakeups); } /* Return code tells caller if we had to escape a deadlock or not */ diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 3d1049cf75..4ce7d53368 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -374,14 +374,14 @@ static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc, static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner); static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode); static void FinishStrongLockAcquire(void); -static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner); +static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, LatchGroup *wakeups); static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock); static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent); static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode, PROCLOCK *proclock, LockMethod lockMethodTable); static void CleanUpLock(LOCK *lock, PROCLOCK *proclock, LockMethod lockMethodTable, uint32 hashcode, - bool wakeupNeeded); + bool wakeupNeeded, LatchGroup *wakeups); static void LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, LOCKTAG *locktag, LOCKMODE lockmode, bool decrement_strong_lock_count); @@ -787,6 +787,7 @@ LockAcquireExtended(const LOCKTAG *locktag, LWLock *partitionLock; bool found_conflict; bool log_lock = false; + LatchGroup wakeups = {0}; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) elog(ERROR, "unrecognized lock method: %d", lockmethodid); @@ -1098,7 +1099,7 @@ LockAcquireExtended(const LOCKTAG *locktag, locktag->locktag_type, lockmode); - WaitOnLock(locallock, owner); + WaitOnLock(locallock, owner, &wakeups); TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1, locktag->locktag_field2, @@ -1138,6 +1139,8 @@ LockAcquireExtended(const LOCKTAG *locktag, LWLockRelease(partitionLock); + SetLatches(&wakeups); + /* * Emit a WAL record if acquisition of this lock needs to be replayed in a * standby server. @@ -1634,7 +1637,7 @@ UnGrantLock(LOCK *lock, LOCKMODE lockmode, static void CleanUpLock(LOCK *lock, PROCLOCK *proclock, LockMethod lockMethodTable, uint32 hashcode, - bool wakeupNeeded) + bool wakeupNeeded, LatchGroup *wakeups) { /* * If this was my last hold on this lock, delete my entry in the proclock @@ -1674,7 +1677,7 @@ CleanUpLock(LOCK *lock, PROCLOCK *proclock, else if (wakeupNeeded) { /* There are waiters on this lock, so wake them up. */ - ProcLockWakeup(lockMethodTable, lock); + ProcLockWakeup(lockMethodTable, lock, wakeups); } } @@ -1811,7 +1814,7 @@ MarkLockClear(LOCALLOCK *locallock) * The appropriate partition lock must be held at entry. */ static void -WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner) +WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, LatchGroup *wakeups) { LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock); LockMethod lockMethodTable = LockMethods[lockmethodid]; @@ -1856,7 +1859,7 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner) */ PG_TRY(); { - if (ProcSleep(locallock, lockMethodTable) != PROC_WAIT_STATUS_OK) + if (ProcSleep(locallock, lockMethodTable, wakeups) != PROC_WAIT_STATUS_OK) { /* * We failed as a result of a deadlock, see CheckDeadLock(). Quit @@ -1915,7 +1918,7 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner) * NB: this does not clean up any locallock object that may exist for the lock. */ void -RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode) +RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode, LatchGroup *wakeups) { LOCK *waitLock = proc->waitLock; PROCLOCK *proclock = proc->waitProcLock; @@ -1957,7 +1960,7 @@ RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode) */ CleanUpLock(waitLock, proclock, LockMethods[lockmethodid], hashcode, - true); + true, wakeups); } /* @@ -1982,6 +1985,7 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) PROCLOCK *proclock; LWLock *partitionLock; bool wakeupNeeded; + LatchGroup wakeups = {0}; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) elog(ERROR, "unrecognized lock method: %d", lockmethodid); @@ -2160,10 +2164,12 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) CleanUpLock(lock, proclock, lockMethodTable, locallock->hashcode, - wakeupNeeded); + wakeupNeeded, &wakeups); LWLockRelease(partitionLock); + SetLatches(&wakeups); + RemoveLocalLock(locallock); return true; } @@ -2188,6 +2194,7 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) PROCLOCK *proclock; int partition; bool have_fast_path_lwlock = false; + LatchGroup wakeups = {0}; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) elog(ERROR, "unrecognized lock method: %d", lockmethodid); @@ -2434,10 +2441,12 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) CleanUpLock(lock, proclock, lockMethodTable, LockTagHashCode(&lock->tag), - wakeupNeeded); + wakeupNeeded, &wakeups); } /* loop over PROCLOCKs within this partition */ LWLockRelease(partitionLock); + + SetLatches(&wakeups); } /* loop over partitions */ #ifdef LOCK_DEBUG @@ -3137,6 +3146,7 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, uint32 proclock_hashcode; LWLock *partitionLock; bool wakeupNeeded; + LatchGroup wakeups = {0}; hashcode = LockTagHashCode(locktag); partitionLock = LockHashPartitionLock(hashcode); @@ -3190,10 +3200,12 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, CleanUpLock(lock, proclock, lockMethodTable, hashcode, - wakeupNeeded); + wakeupNeeded, &wakeups); LWLockRelease(partitionLock); + SetLatches(&wakeups); + /* * Decrement strong lock count. This logic is needed only for 2PC. */ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 13fa07b0ff..a593c11f45 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -700,6 +700,7 @@ LockErrorCleanup(void) { LWLock *partitionLock; DisableTimeoutParams timeouts[2]; + LatchGroup wakeups = {0}; HOLD_INTERRUPTS(); @@ -733,7 +734,7 @@ LockErrorCleanup(void) if (MyProc->links.next != NULL) { /* We could not have been granted the lock yet */ - RemoveFromWaitQueue(MyProc, lockAwaited->hashcode); + RemoveFromWaitQueue(MyProc, lockAwaited->hashcode, &wakeups); } else { @@ -750,6 +751,7 @@ LockErrorCleanup(void) lockAwaited = NULL; LWLockRelease(partitionLock); + SetLatches(&wakeups); RESUME_INTERRUPTS(); } @@ -1042,7 +1044,7 @@ ProcQueueInit(PROC_QUEUE *queue) * NOTES: The process queue is now a priority queue for locking. */ ProcWaitStatus -ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) +ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchGroup *wakeups) { LOCKMODE lockmode = locallock->tag.mode; LOCK *lock = locallock->lock; @@ -1188,7 +1190,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) */ if (early_deadlock) { - RemoveFromWaitQueue(MyProc, hashcode); + RemoveFromWaitQueue(MyProc, hashcode, wakeups); return PROC_WAIT_STATUS_ERROR; } @@ -1204,6 +1206,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * LockErrorCleanup will clean up if cancel/die happens. */ LWLockRelease(partitionLock); + SetLatches(wakeups); /* * Also, now that we will successfully clean up after an ereport, it's @@ -1662,8 +1665,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable) * to twiddle the lock's request counts too --- see RemoveFromWaitQueue. * Hence, in practice the waitStatus parameter must be PROC_WAIT_STATUS_OK. */ -PGPROC * -ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus) +static PGPROC * +ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus, LatchGroup *wakeups) { PGPROC *retProc; @@ -1686,8 +1689,8 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus) proc->waitStatus = waitStatus; pg_atomic_write_u64(&MyProc->waitStart, 0); - /* And awaken it */ - SetLatch(&proc->procLatch); + /* Schedule it to be awoken */ + AddLatch(wakeups, &proc->procLatch); return retProc; } @@ -1700,7 +1703,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus) * The appropriate lock partition lock must be held by caller. */ void -ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock) +ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock, LatchGroup *wakeups) { PROC_QUEUE *waitQueue = &(lock->waitProcs); int queue_size = waitQueue->size; @@ -1728,7 +1731,7 @@ ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock) { /* OK to waken */ GrantLock(lock, proc->waitProcLock, lockmode); - proc = ProcWakeup(proc, PROC_WAIT_STATUS_OK); + proc = ProcWakeup(proc, PROC_WAIT_STATUS_OK, wakeups); /* * ProcWakeup removes proc from the lock's waiting process queue @@ -1762,6 +1765,7 @@ static void CheckDeadLock(void) { int i; + LatchGroup wakeups = {0}; /* * Acquire exclusive lock on the entire shared lock data structures. Must @@ -1796,7 +1800,7 @@ CheckDeadLock(void) #endif /* Run the deadlock check, and set deadlock_state for use by ProcSleep */ - deadlock_state = DeadLockCheck(MyProc); + deadlock_state = DeadLockCheck(MyProc, &wakeups); if (deadlock_state == DS_HARD_DEADLOCK) { @@ -1813,7 +1817,8 @@ CheckDeadLock(void) * return from the signal handler. */ Assert(MyProc->waitLock != NULL); - RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag))); + RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)), + &wakeups); /* * We're done here. Transaction abort caused by the error that @@ -1837,6 +1842,8 @@ CheckDeadLock(void) check_done: for (i = NUM_LOCK_PARTITIONS; --i >= 0;) LWLockRelease(LockHashPartitionLockByIndex(i)); + + SetLatches(&wakeups); } /* diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index e4e1495b24..790c15c7ea 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -19,6 +19,7 @@ #endif #include "storage/backendid.h" +#include "storage/latch.h" #include "storage/lockdefs.h" #include "storage/lwlock.h" #include "storage/shmem.h" @@ -575,7 +576,7 @@ extern bool LockCheckConflicts(LockMethod lockMethodTable, LOCK *lock, PROCLOCK *proclock); extern void GrantLock(LOCK *lock, PROCLOCK *proclock, LOCKMODE lockmode); extern void GrantAwaitedLock(void); -extern void RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode); +extern void RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode, LatchGroup *wakeups); extern Size LockShmemSize(void); extern LockData *GetLockStatusData(void); extern BlockedProcsData *GetBlockerStatusData(int blocked_pid); @@ -592,7 +593,7 @@ extern void lock_twophase_postabort(TransactionId xid, uint16 info, extern void lock_twophase_standby_recover(TransactionId xid, uint16 info, void *recdata, uint32 len); -extern DeadLockState DeadLockCheck(PGPROC *proc); +extern DeadLockState DeadLockCheck(PGPROC *proc, LatchGroup *wakeups); extern PGPROC *GetBlockingAutoVacuumPgproc(void); extern void DeadLockReport(void) pg_attribute_noreturn(); extern void RememberSimpleDeadLock(PGPROC *proc1, diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 8d096fdeeb..0ae47325e4 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -449,9 +449,8 @@ extern bool HaveNFreeProcs(int n); extern void ProcReleaseLocks(bool isCommit); extern void ProcQueueInit(PROC_QUEUE *queue); -extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable); -extern PGPROC *ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus); -extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock); +extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchGroup *wakeups); +extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock, LatchGroup *wakeups); extern void CheckDeadLockAlert(void); extern bool IsWaitingForLock(void); extern void LockErrorCleanup(void); -- 2.37.0 (Apple Git-136) [application/octet-stream] v2-0005-Don-t-re-acquire-LockManager-partition-lock-after.patch (6.3K, ../../CA+hUKG+UfnpJwxUJO-XoAJ-JabXAhmkTwwjG8HLfi7XiQz_bxA@mail.gmail.com/6-v2-0005-Don-t-re-acquire-LockManager-partition-lock-after.patch) download | inline diff: From 162b7203e5f355649de2381ad6b3abc92acc3f3a Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Thu, 27 Oct 2022 10:56:39 +1300 Subject: [PATCH v2 5/9] Don't re-acquire LockManager partition lock after wakeup. Change the contract of WaitOnLock() and ProcSleep() to return with the partition lock released. ProcSleep() re-acquired the lock to prevent interrupts, which was a problem at the time the ancestor of that code was committed in fe548629c50, because then we had signal handlers that longjmp'd out of there. Now, that can happen only at points where we explicitly run CHECK_FOR_INTERRUPTS(), so we can remove the lock, which leads to the idea of making the function always release. While an earlier commit fixed the problem that the backend woke us up before it had even released the lock itself, this lock reacquisition point was still contended when multiple backends woke at the same time. XXX Right? Or what am I missing? Discussion: https://postgr.es/m/CA%2BhUKGKmO7ze0Z6WXKdrLxmvYa%3DzVGGXOO30MMktufofVwEm1A%40mail.gmail.com --- src/backend/storage/lmgr/lock.c | 18 ++++++++++++------ src/backend/storage/lmgr/proc.c | 20 ++++++++++++-------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 4ce7d53368..cd3b84101c 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -787,6 +787,7 @@ LockAcquireExtended(const LOCKTAG *locktag, LWLock *partitionLock; bool found_conflict; bool log_lock = false; + bool partitionLocked = false; LatchGroup wakeups = {0}; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) @@ -995,6 +996,7 @@ LockAcquireExtended(const LOCKTAG *locktag, partitionLock = LockHashPartitionLock(hashcode); LWLockAcquire(partitionLock, LW_EXCLUSIVE); + partitionLocked = true; /* * Find or create lock and proclock entries with this tag @@ -1099,7 +1101,10 @@ LockAcquireExtended(const LOCKTAG *locktag, locktag->locktag_type, lockmode); + Assert(LWLockHeldByMeInMode(partitionLock, LW_EXCLUSIVE)); WaitOnLock(locallock, owner, &wakeups); + Assert(!LWLockHeldByMeInMode(partitionLock, LW_EXCLUSIVE)); + partitionLocked = false; TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1, locktag->locktag_field2, @@ -1124,7 +1129,6 @@ LockAcquireExtended(const LOCKTAG *locktag, PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock); LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode); /* Should we retry ? */ - LWLockRelease(partitionLock); elog(ERROR, "LockAcquire failed"); } PROCLOCK_PRINT("LockAcquire: granted", proclock); @@ -1137,9 +1141,11 @@ LockAcquireExtended(const LOCKTAG *locktag, */ FinishStrongLockAcquire(); - LWLockRelease(partitionLock); - - SetLatches(&wakeups); + if (partitionLocked) + { + LWLockRelease(partitionLock); + SetLatches(&wakeups); + } /* * Emit a WAL record if acquisition of this lock needs to be replayed in a @@ -1811,7 +1817,8 @@ MarkLockClear(LOCALLOCK *locallock) * Caller must have set MyProc->heldLocks to reflect locks already held * on the lockable object by this process. * - * The appropriate partition lock must be held at entry. + * The appropriate partition lock must be held at entry. It is not held on + * exit. */ static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, LatchGroup *wakeups) @@ -1868,7 +1875,6 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, LatchGroup *wakeups) awaitedLock = NULL; LOCK_PRINT("WaitOnLock: aborting on lock", locallock->lock, locallock->tag.mode); - LWLockRelease(LockHashPartitionLock(locallock->hashcode)); /* * Now that we aren't holding the partition lock, we can give an diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index a593c11f45..281260dab3 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1033,7 +1033,7 @@ ProcQueueInit(PROC_QUEUE *queue) * Caller must have set MyProc->heldLocks to reflect locks already held * on the lockable object by this process (under all XIDs). * - * The lock table's partition lock must be held at entry, and will be held + * The lock table's partition lock must be held at entry, and will be released * at exit. * * Result: PROC_WAIT_STATUS_OK if we acquired the lock, PROC_WAIT_STATUS_ERROR if not (deadlock). @@ -1062,6 +1062,12 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchGroup *wakeups) PGPROC *leader = MyProc->lockGroupLeader; int i; + /* + * Every way out of this function will release the partition lock and send + * buffered latch wakeups. + */ + Assert(LWLockHeldByMeInMode(partitionLock, LW_EXCLUSIVE)); + /* * If group locking is in use, locks held by members of my locking group * need to be included in myHeldLocks. This is not required for relation @@ -1148,6 +1154,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchGroup *wakeups) /* Skip the wait and just grant myself the lock. */ GrantLock(lock, proclock, lockmode); GrantAwaitedLock(); + LWLockRelease(partitionLock); + SetLatches(wakeups); return PROC_WAIT_STATUS_OK; } /* Break out of loop to put myself before him */ @@ -1191,6 +1199,8 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchGroup *wakeups) if (early_deadlock) { RemoveFromWaitQueue(MyProc, hashcode, wakeups); + LWLockRelease(partitionLock); + SetLatches(wakeups); return PROC_WAIT_STATUS_ERROR; } @@ -1525,6 +1535,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchGroup *wakeups) } LWLockRelease(partitionLock); + SetLatches(wakeups); if (deadlock_state == DS_SOFT_DEADLOCK) ereport(LOG, @@ -1626,13 +1637,6 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, LatchGroup *wakeups) standbyWaitStart, GetCurrentTimestamp(), NULL, false); - /* - * Re-acquire the lock table's partition lock. We have to do this to hold - * off cancel/die interrupts before we can mess with lockAwaited (else we - * might have a missed or duplicated locallock update). - */ - LWLockAcquire(partitionLock, LW_EXCLUSIVE); - /* * We no longer want LockErrorCleanup to do anything. */ -- 2.37.0 (Apple Git-136) [application/octet-stream] v2-0006-Use-SetLatches-for-SERIALIZABLE-DEFERRABLE-wakeup.patch (4.7K, ../../CA+hUKG+UfnpJwxUJO-XoAJ-JabXAhmkTwwjG8HLfi7XiQz_bxA@mail.gmail.com/7-v2-0006-Use-SetLatches-for-SERIALIZABLE-DEFERRABLE-wakeup.patch) download | inline diff: From 0e7ac0c8ae5f7d151412fb61ba582394e60271cf Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Thu, 27 Oct 2022 12:40:12 +1300 Subject: [PATCH v2 6/9] Use SetLatches() for SERIALIZABLE DEFERRABLE wakeups. Don't issue SetLatch()'s system call while holding a highly contended LWLock. Collect them in a LatchBatch to be set after the lock is released. Once woken, other backends will immediately try to acquire that lock anyway so it's better to wake after releasing. Take this opportunity to retire the confusingly named ProcSendSignal() and replace it with something that gives the latch pointer we need. There was only one other caller, in bufmgr.c, which is easily changed. Discussion: https://postgr.es/m/CA%2BhUKGKmO7ze0Z6WXKdrLxmvYa%3DzVGGXOO30MMktufofVwEm1A%40mail.gmail.com --- src/backend/storage/buffer/bufmgr.c | 2 +- src/backend/storage/ipc/procsignal.c | 2 -- src/backend/storage/lmgr/predicate.c | 5 ++++- src/backend/storage/lmgr/proc.c | 12 ------------ src/include/storage/proc.h | 4 +++- 5 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 73d30bf619..df15f5e6c2 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -1918,7 +1918,7 @@ UnpinBuffer(BufferDesc *buf) buf_state &= ~BM_PIN_COUNT_WAITER; UnlockBufHdr(buf, buf_state); - ProcSendSignal(wait_backend_pgprocno); + SetLatch(GetProcLatchByNumber(wait_backend_pgprocno)); } else UnlockBufHdr(buf, buf_state); diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index 7767657f27..280e4b91dd 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -254,8 +254,6 @@ CleanupProcSignalState(int status, Datum arg) * * On success (a signal was sent), zero is returned. * On error, -1 is returned, and errno is set (typically to ESRCH or EPERM). - * - * Not to be confused with ProcSendSignal */ int SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId) diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index e8120174d6..c62adea6b3 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -3339,6 +3339,7 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe) nextConflict, possibleUnsafeConflict; SERIALIZABLEXACT *roXact; + LatchGroup wakeups = {0}; /* * We can't trust XactReadOnly here, because a transaction which started @@ -3672,7 +3673,7 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe) */ if (SxactIsDeferrableWaiting(roXact) && (SxactIsROUnsafe(roXact) || SxactIsROSafe(roXact))) - ProcSendSignal(roXact->pgprocno); + AddLatch(&wakeups, GetProcLatchByNumber(roXact->pgprocno)); possibleUnsafeConflict = nextConflict; } @@ -3698,6 +3699,8 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe) LWLockRelease(SerializableXactHashLock); + SetLatches(&wakeups); + LWLockAcquire(SerializableFinishedListLock, LW_EXCLUSIVE); /* Add this to the list of transactions to check for later cleanup. */ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 281260dab3..48f962e6bd 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1890,18 +1890,6 @@ ProcWaitForSignal(uint32 wait_event_info) CHECK_FOR_INTERRUPTS(); } -/* - * ProcSendSignal - set the latch of a backend identified by pgprocno - */ -void -ProcSendSignal(int pgprocno) -{ - if (pgprocno < 0 || pgprocno >= ProcGlobal->allProcCount) - elog(ERROR, "pgprocno out of range"); - - SetLatch(&ProcGlobal->allProcs[pgprocno].procLatch); -} - /* * BecomeLockGroupLeader - designate process as lock group leader * diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 0ae47325e4..ab17cb66b8 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -413,6 +413,9 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; /* Accessor for PGPROC given a pgprocno. */ #define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)]) +/* Accessor for procLatch given a pgprocno. */ +#define GetProcLatchByNumber(n) (&(GetPGProcByNumber(n))->procLatch) + /* * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. @@ -456,7 +459,6 @@ extern bool IsWaitingForLock(void); extern void LockErrorCleanup(void); extern void ProcWaitForSignal(uint32 wait_event_info); -extern void ProcSendSignal(int pgprocno); extern PGPROC *AuxiliaryPidGetProc(int pid); -- 2.37.0 (Apple Git-136) [application/octet-stream] v2-0007-Use-SetLatches-for-synchronous-replication-wakeup.patch (3.4K, ../../CA+hUKG+UfnpJwxUJO-XoAJ-JabXAhmkTwwjG8HLfi7XiQz_bxA@mail.gmail.com/8-v2-0007-Use-SetLatches-for-synchronous-replication-wakeup.patch) download | inline diff: From e195322f6ba9f2bfbd830b6d13f0a5d8e6f0d67c Mon Sep 17 00:00:00 2001 From: Thomas Munro <[email protected]> Date: Thu, 27 Oct 2022 12:59:45 +1300 Subject: [PATCH v2 7/9] Use SetLatches() for synchronous replication wakeups. Don't issue SetLatch() system calls while holding SyncRepLock. Discussion: https://postgr.es/m/CA%2BhUKGKmO7ze0Z6WXKdrLxmvYa%3DzVGGXOO30MMktufofVwEm1A%40mail.gmail.com --- src/backend/replication/syncrep.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index 1a022b11a0..e2410ff494 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -100,7 +100,7 @@ static int SyncRepWaitMode = SYNC_REP_NO_WAIT; static void SyncRepQueueInsert(int mode); static void SyncRepCancelWait(void); -static int SyncRepWakeQueue(bool all, int mode); +static int SyncRepWakeQueue(bool all, int mode, LatchGroup *wakeups); static bool SyncRepGetSyncRecPtr(XLogRecPtr *writePtr, XLogRecPtr *flushPtr, @@ -449,6 +449,7 @@ SyncRepReleaseWaiters(void) int numwrite = 0; int numflush = 0; int numapply = 0; + LatchGroup wakeups = {0}; /* * If this WALSender is serving a standby that is not on the list of @@ -518,21 +519,23 @@ SyncRepReleaseWaiters(void) if (walsndctl->lsn[SYNC_REP_WAIT_WRITE] < writePtr) { walsndctl->lsn[SYNC_REP_WAIT_WRITE] = writePtr; - numwrite = SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE); + numwrite = SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE, &wakeups); } if (walsndctl->lsn[SYNC_REP_WAIT_FLUSH] < flushPtr) { walsndctl->lsn[SYNC_REP_WAIT_FLUSH] = flushPtr; - numflush = SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH); + numflush = SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH, &wakeups); } if (walsndctl->lsn[SYNC_REP_WAIT_APPLY] < applyPtr) { walsndctl->lsn[SYNC_REP_WAIT_APPLY] = applyPtr; - numapply = SyncRepWakeQueue(false, SYNC_REP_WAIT_APPLY); + numapply = SyncRepWakeQueue(false, SYNC_REP_WAIT_APPLY, &wakeups); } LWLockRelease(SyncRepLock); + SetLatches(&wakeups); + elog(DEBUG3, "released %d procs up to write %X/%X, %d procs up to flush %X/%X, %d procs up to apply %X/%X", numwrite, LSN_FORMAT_ARGS(writePtr), numflush, LSN_FORMAT_ARGS(flushPtr), @@ -876,7 +879,7 @@ SyncRepGetStandbyPriority(void) * The caller must hold SyncRepLock in exclusive mode. */ static int -SyncRepWakeQueue(bool all, int mode) +SyncRepWakeQueue(bool all, int mode, LatchGroup *wakeups) { volatile WalSndCtlData *walsndctl = WalSndCtl; PGPROC *proc = NULL; @@ -929,7 +932,7 @@ SyncRepWakeQueue(bool all, int mode) /* * Wake only when we have set state and removed from queue. */ - SetLatch(&(thisproc->procLatch)); + AddLatch(wakeups, &thisproc->procLatch); numprocs++; } @@ -951,6 +954,8 @@ SyncRepUpdateSyncStandbysDefined(void) if (sync_standbys_defined != WalSndCtl->sync_standbys_defined) { + LatchGroup wakeups = {0}; + LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); /* @@ -963,7 +968,7 @@ SyncRepUpdateSyncStandbysDefined(void) int i; for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++) - SyncRepWakeQueue(true, i); + SyncRepWakeQueue(true, i, &wakeups); } /* @@ -976,6 +981,8 @@ SyncRepUpdateSyncStandbysDefined(void) WalSndCtl->sync_standbys_defined = sync_standbys_defined; LWLockRelease(SyncRepLock); + + SetLatches(&wakeups); } } -- 2.37.0 (Apple Git-136) ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Latches vs lwlock contention @ 2024-09-10 16:53 Maxim Orlov <[email protected]> parent: Thomas Munro <[email protected]> 1 sibling, 1 reply; 7+ messages in thread From: Maxim Orlov @ 2024-09-10 16:53 UTC (permalink / raw) To: Heikki Linnakangas <[email protected]>; +Cc: Thomas Munro <[email protected]>; pgsql-hackers; Yura Sokolov <[email protected]>; Andres Freund <[email protected]> I looked at the patch set and found it quite useful. The first 7 patches are just refactoring and may be committed separately if needed. There were minor problems: patch #5 don't want to apply clearly and the #8 is complained about partitionLock is unused if we build without asserts. So, I add a PG_USED_FOR_ASSERTS_ONLY to solve the last issue. Again, overall patch looks good and seems useful to me. Here is the rebased v5 version based on Heikki's patch set above. -- Best regards, Maxim Orlov. Attachments: [application/octet-stream] v5-0004-Move-TRACE-calls-into-WaitOnLock.patch (2.5K, ../../CACG=ezYS6sKbWa=Py23jH8P-SXGbZ+z6T_rYaEwfjGfh_yLgrQ@mail.gmail.com/3-v5-0004-Move-TRACE-calls-into-WaitOnLock.patch) download | inline diff: From 6f381e04a3201c6926190b234f128377ca3f84f4 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <[email protected]> Date: Mon, 22 Jul 2024 13:12:57 +0300 Subject: [PATCH v5 4/8] Move TRACE calls into WaitOnLock() LockAcquire is a long and complex function. Pushing more stuff to its subroutines makes it a little more manageable. --- src/backend/storage/lmgr/lock.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index abac7134da..e82a041097 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -1051,23 +1051,8 @@ LockAcquireExtended(const LOCKTAG *locktag, * case, because while trying to go to sleep, we may discover that we * can acquire the lock immediately after all. */ - - TRACE_POSTGRESQL_LOCK_WAIT_START(locktag->locktag_field1, - locktag->locktag_field2, - locktag->locktag_field3, - locktag->locktag_field4, - locktag->locktag_type, - lockmode); - WaitOnLock(locallock, owner, dontWait); - TRACE_POSTGRESQL_LOCK_WAIT_DONE(locktag->locktag_field1, - locktag->locktag_field2, - locktag->locktag_field3, - locktag->locktag_field4, - locktag->locktag_type, - lockmode); - /* * NOTE: do not do any material change of state between here and * return. All required changes in locktable state must have been @@ -1811,6 +1796,13 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, bool dontWait) LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock); LockMethod lockMethodTable = LockMethods[lockmethodid]; + TRACE_POSTGRESQL_LOCK_WAIT_START(locallock->tag.lock.locktag_field1, + locallock->tag.lock.locktag_field2, + locallock->tag.lock.locktag_field3, + locallock->tag.lock.locktag_field4, + locallock->tag.lock.locktag_type, + locallock->tag.mode); + LOCK_PRINT("WaitOnLock: sleeping on lock", locallock->lock, locallock->tag.mode); @@ -1881,6 +1873,13 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, bool dontWait) LOCK_PRINT("WaitOnLock: wakeup on lock", locallock->lock, locallock->tag.mode); + + TRACE_POSTGRESQL_LOCK_WAIT_DONE(locallock->tag.lock.locktag_field1, + locallock->tag.lock.locktag_field2, + locallock->tag.lock.locktag_field3, + locallock->tag.lock.locktag_field4, + locallock->tag.lock.locktag_type, + locallock->tag.mode); } /* -- 2.34.1 [application/octet-stream] v5-0001-Remove-LOCK_PRINT-call-that-could-point-to-garbag.patch (1.1K, ../../CACG=ezYS6sKbWa=Py23jH8P-SXGbZ+z6T_rYaEwfjGfh_yLgrQ@mail.gmail.com/4-v5-0001-Remove-LOCK_PRINT-call-that-could-point-to-garbag.patch) download | inline diff: From e7c1d682a6d7ff2104a17659a0546c91b390601a Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <[email protected]> Date: Mon, 22 Jul 2024 01:21:20 +0300 Subject: [PATCH v5 1/8] Remove LOCK_PRINT() call that could point to garbage If a deadlock is detected in ProcSleep, it removes the process from the wait queue and the shared LOCK and PROCLOCK entries. As soon as it does that, the other process holding the lock might release it, and remove the LOCK entry altogether. So on return from ProcSleep(), it's possible that the LOCK entry is no longer valid. --- src/backend/storage/lmgr/lock.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 83b99a98f0..4d2b510835 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -1860,8 +1860,6 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, bool dontWait) * now. */ awaitedLock = NULL; - LOCK_PRINT("WaitOnLock: aborting on lock", - locallock->lock, locallock->tag.mode); LWLockRelease(LockHashPartitionLock(locallock->hashcode)); /* -- 2.34.1 [application/octet-stream] v5-0003-Set-MyProc-heldLocks-in-ProcSleep.patch (3.1K, ../../CACG=ezYS6sKbWa=Py23jH8P-SXGbZ+z6T_rYaEwfjGfh_yLgrQ@mail.gmail.com/5-v5-0003-Set-MyProc-heldLocks-in-ProcSleep.patch) download | inline diff: From f1ee1178fa214cce8885b99ff76441781e0816af Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <[email protected]> Date: Mon, 22 Jul 2024 13:23:59 +0300 Subject: [PATCH v5 3/8] Set MyProc->heldLocks in ProcSleep Previously, the ProcSleep() caller was respnsible for setting it, and we had comments to remind about that. But it seems simpler to make ProcSleep() itself responsible for it. ProcSleep() already set the other info about the lock its waiting for (waitLock, waitProcLock and waitLockMode), so seems natural for it to set heldLocks too. --- src/backend/storage/lmgr/lock.c | 8 -------- src/backend/storage/lmgr/proc.c | 10 ++++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 68dff44495..abac7134da 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -1046,11 +1046,6 @@ LockAcquireExtended(const LOCKTAG *locktag, } else { - /* - * Set bitmask of locks this process already holds on this object. - */ - MyProc->heldLocks = proclock->holdMask; - /* * Sleep till someone wakes me up. We do this even in the dontWait * case, because while trying to go to sleep, we may discover that we @@ -1807,9 +1802,6 @@ MarkLockClear(LOCALLOCK *locallock) /* * WaitOnLock -- wait to acquire a lock * - * Caller must have set MyProc->heldLocks to reflect locks already held - * on the lockable object by this process. - * * The appropriate partition lock must be held at entry, and will still be * held at exit. */ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index ac66da8638..9aff57a519 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1046,9 +1046,6 @@ AuxiliaryPidGetProc(int pid) /* * ProcSleep -- put a process to sleep on the specified lock * - * Caller must have set MyProc->heldLocks to reflect locks already held - * on the lockable object by this process (under all XIDs). - * * It's not actually guaranteed that we need to wait when this function is * called, because it could be that when we try to find a position at which * to insert ourself into the wait queue, we discover that we must be inserted @@ -1078,7 +1075,7 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) LWLock *partitionLock = LockHashPartitionLock(hashcode); dclist_head *waitQueue = &lock->waitProcs; PGPROC *insert_before = NULL; - LOCKMASK myHeldLocks = MyProc->heldLocks; + LOCKMASK myHeldLocks; TimestampTz standbyWaitStart = 0; bool early_deadlock = false; bool allow_autovacuum_cancel = true; @@ -1086,6 +1083,11 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) ProcWaitStatus myWaitStatus; PGPROC *leader = MyProc->lockGroupLeader; + /* + * Set bitmask of locks this process already holds on this object. + */ + myHeldLocks = MyProc->heldLocks = proclock->holdMask; + /* * If group locking is in use, locks held by members of my locking group * need to be included in myHeldLocks. This is not required for relation -- 2.34.1 [application/octet-stream] v5-0005-Remove-redundant-lockAwaited-global-variable.patch (5.3K, ../../CACG=ezYS6sKbWa=Py23jH8P-SXGbZ+z6T_rYaEwfjGfh_yLgrQ@mail.gmail.com/6-v5-0005-Remove-redundant-lockAwaited-global-variable.patch) download | inline diff: From ae12e4be3b34ffac896d7c406f8a5f4ad5815579 Mon Sep 17 00:00:00 2001 From: Maxim Orlov <[email protected]> Date: Tue, 10 Sep 2024 18:58:09 +0300 Subject: [PATCH v5 5/8] Remove redundant lockAwaited global variable We had two global (static) variables to hold the LOCALLOCK that we are currently acquiring: awaitedLock in lock.c and lockAwaited in proc.c. They were set at slightly different times, but when they were both set, they were the same thing. Remove the lockAwaited variable and rely on awaitedLock everywhere. --- src/backend/storage/lmgr/lock.c | 9 +++++++++ src/backend/storage/lmgr/proc.c | 30 ++++-------------------------- src/backend/tcop/postgres.c | 2 +- src/include/storage/lock.h | 2 ++ src/include/storage/proc.h | 1 - 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index e82a041097..4e1890276f 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -1770,6 +1770,12 @@ GrantAwaitedLock(void) GrantLockLocal(awaitedLock, awaitedOwner); } +LOCALLOCK * +GetAwaitedLock(void) +{ + return awaitedLock; +} + /* * MarkLockClear -- mark an acquired lock as "clear" * @@ -1866,6 +1872,9 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, bool dontWait) } PG_END_TRY(); + /* + * We no longer want LockErrorCleanup to do anything. + */ awaitedLock = NULL; /* reset ps display to remove the suffix */ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 9aff57a519..e8084dd3d5 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -80,9 +80,6 @@ PROC_HDR *ProcGlobal = NULL; NON_EXEC_STATIC PGPROC *AuxiliaryProcs = NULL; PGPROC *PreparedXactProcs = NULL; -/* If we are waiting for a lock, this points to the associated LOCALLOCK */ -static LOCALLOCK *lockAwaited = NULL; - static DeadLockState deadlock_state = DS_NOT_YET_CHECKED; /* Is a deadlock check pending? */ @@ -707,18 +704,6 @@ HaveNFreeProcs(int n, int *nfree) return (*nfree == n); } -/* - * Check if the current process is awaiting a lock. - */ -bool -IsWaitingForLock(void) -{ - if (lockAwaited == NULL) - return false; - - return true; -} - /* * Cancel any pending wait for lock, when aborting a transaction, and revert * any strong lock count acquisition for a lock being acquired. @@ -730,6 +715,7 @@ IsWaitingForLock(void) void LockErrorCleanup(void) { + LOCALLOCK *lockAwaited; LWLock *partitionLock; DisableTimeoutParams timeouts[2]; @@ -738,6 +724,7 @@ LockErrorCleanup(void) AbortStrongLockAcquire(); /* Nothing to do if we weren't waiting for a lock */ + lockAwaited = GetAwaitedLock(); if (lockAwaited == NULL) { RESUME_INTERRUPTS(); @@ -1218,9 +1205,6 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) return PROC_WAIT_STATUS_ERROR; } - /* mark that we are waiting for a lock */ - lockAwaited = locallock; - /* * Release the lock table's partition lock. * @@ -1645,17 +1629,11 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) NULL, false); /* - * Re-acquire the lock table's partition lock. We have to do this to hold - * off cancel/die interrupts before we can mess with lockAwaited (else we - * might have a missed or duplicated locallock update). + * Re-acquire the lock table's partition lock, because the caller expects + * us to still hold it. */ LWLockAcquire(partitionLock, LW_EXCLUSIVE); - /* - * We no longer want LockErrorCleanup to do anything. - */ - lockAwaited = NULL; - /* * If we got the lock, be sure to remember it in the locallock table. */ diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 8bc6bea113..9b800b72bf 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3062,7 +3062,7 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) /* * If we aren't waiting for a lock we can never deadlock. */ - if (!IsWaitingForLock()) + if (GetAwaitedLock() == NULL) return; /* Intentional fall through to check wait for pin */ diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index 8b328a06d9..787f3db06a 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -585,6 +585,8 @@ extern bool LockCheckConflicts(LockMethod lockMethodTable, LOCK *lock, PROCLOCK *proclock); extern void GrantLock(LOCK *lock, PROCLOCK *proclock, LOCKMODE lockmode); extern void GrantAwaitedLock(void); +extern LOCALLOCK *GetAwaitedLock(void); + extern void RemoveFromWaitQueue(PGPROC *proc, uint32 hashcode); extern LockData *GetLockStatusData(void); extern BlockedProcsData *GetBlockerStatusData(int blocked_pid); diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index deeb06c9e0..7ddfc8f392 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -481,7 +481,6 @@ extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, extern void ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus); extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock); extern void CheckDeadLockAlert(void); -extern bool IsWaitingForLock(void); extern void LockErrorCleanup(void); extern void ProcWaitForSignal(uint32 wait_event_info); -- 2.34.1 [application/octet-stream] v5-0002-Fix-comment-in-LockReleaseAll-on-when-locallock-n.patch (1.1K, ../../CACG=ezYS6sKbWa=Py23jH8P-SXGbZ+z6T_rYaEwfjGfh_yLgrQ@mail.gmail.com/7-v5-0002-Fix-comment-in-LockReleaseAll-on-when-locallock-n.patch) download | inline diff: From 4660001bf25079c35e584be03c8a4dcf7b8ff1e0 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <[email protected]> Date: Mon, 22 Jul 2024 00:43:13 +0300 Subject: [PATCH v5 2/8] Fix comment in LockReleaseAll() on when locallock->nLock can be zero We reach this case also e.g. when a deadlock is detected, not only when we run out of memory. --- src/backend/storage/lmgr/lock.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 4d2b510835..68dff44495 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -2207,9 +2207,8 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) { /* - * If the LOCALLOCK entry is unused, we must've run out of shared - * memory while trying to set up this lock. Just forget the local - * entry. + * If the LOCALLOCK entry is unused, something must've gone wrong + * while trying to acquire this lock. Just forget the local entry. */ if (locallock->nLocks == 0) { -- 2.34.1 [application/octet-stream] v5-0007-Release-partition-lock-a-little-earlier.patch (1.4K, ../../CACG=ezYS6sKbWa=Py23jH8P-SXGbZ+z6T_rYaEwfjGfh_yLgrQ@mail.gmail.com/8-v5-0007-Release-partition-lock-a-little-earlier.patch) download | inline diff: From dc32923f690390f120fccc154d656ff6529d6302 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <[email protected]> Date: Mon, 22 Jul 2024 17:38:06 +0300 Subject: [PATCH v5 7/8] Release partition lock a little earlier We don't need to hold the lock to prevent die/cancel interrupts, they are only processed at explicit CHECK_FOR_INTERRUPTS() points now. --- src/backend/storage/lmgr/lock.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 91d2402609..fc77c88747 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -1042,6 +1042,7 @@ LockAcquireExtended(const LOCKTAG *locktag, { /* No conflict with held or previously requested locks */ GrantLock(lock, proclock, lockmode); + LWLockRelease(partitionLock); } else { @@ -1117,8 +1118,10 @@ LockAcquireExtended(const LOCKTAG *locktag, elog(ERROR, "LockAcquire failed"); } } + PROCLOCK_PRINT("LockAcquire: granted", proclock); LOCK_PRINT("LockAcquire: granted", lock, lockmode); + LWLockRelease(partitionLock); } /* The lock was granted to us. Update the local lock entry accordingly */ @@ -1130,8 +1133,6 @@ LockAcquireExtended(const LOCKTAG *locktag, */ FinishStrongLockAcquire(); - LWLockRelease(partitionLock); - /* * Emit a WAL record if acquisition of this lock needs to be replayed in a * standby server. -- 2.34.1 [application/octet-stream] v5-0006-Update-local-lock-table-in-ProcSleep-s-caller.patch (2.1K, ../../CACG=ezYS6sKbWa=Py23jH8P-SXGbZ+z6T_rYaEwfjGfh_yLgrQ@mail.gmail.com/9-v5-0006-Update-local-lock-table-in-ProcSleep-s-caller.patch) download | inline diff: From 7d5556cabd3955ee25afc55a145d82cefb39b663 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <[email protected]> Date: Mon, 22 Jul 2024 17:27:16 +0300 Subject: [PATCH v5 6/8] Update local lock table in ProcSleep's caller ProcSleep is now responsible only for the shared state. Seems a little nicer that way. --- src/backend/storage/lmgr/lock.c | 4 +++- src/backend/storage/lmgr/proc.c | 7 ------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 4e1890276f..91d2402609 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -1042,7 +1042,6 @@ LockAcquireExtended(const LOCKTAG *locktag, { /* No conflict with held or previously requested locks */ GrantLock(lock, proclock, lockmode); - GrantLockLocal(locallock, owner); } else { @@ -1122,6 +1121,9 @@ LockAcquireExtended(const LOCKTAG *locktag, LOCK_PRINT("LockAcquire: granted", lock, lockmode); } + /* The lock was granted to us. Update the local lock entry accordingly */ + GrantLockLocal(locallock, owner); + /* * Lock state is fully up-to-date now; if we error out after this, no * special error cleanup is required. diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index e8084dd3d5..f29e02e902 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1158,7 +1158,6 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) { /* Skip the wait and just grant myself the lock. */ GrantLock(lock, proclock, lockmode); - GrantAwaitedLock(); return PROC_WAIT_STATUS_OK; } @@ -1634,12 +1633,6 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) */ LWLockAcquire(partitionLock, LW_EXCLUSIVE); - /* - * If we got the lock, be sure to remember it in the locallock table. - */ - if (MyProc->waitStatus == PROC_WAIT_STATUS_OK) - GrantAwaitedLock(); - /* * We don't have to do anything else, because the awaker did all the * necessary update of the lock table and MyProc. -- 2.34.1 [application/octet-stream] v5-0008-Split-ProcSleep-function-into-JoinWaitQueue-and-P.patch (17.6K, ../../CACG=ezYS6sKbWa=Py23jH8P-SXGbZ+z6T_rYaEwfjGfh_yLgrQ@mail.gmail.com/10-v5-0008-Split-ProcSleep-function-into-JoinWaitQueue-and-P.patch) download | inline diff: From ecc3beda97fa00621998b304809a4156f2c1c3c9 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas <[email protected]> Date: Mon, 22 Jul 2024 21:53:18 +0300 Subject: [PATCH v5 8/8] Split ProcSleep function into JoinWaitQueue and ProcSleep Split ProcSleep into two functions: JoinWaitQueue and ProcSleep. JoinWaitQueue is called while holding the partition lock, and inserts the current process to the wait queue, while ProcSleep() does the actual sleeping. ProcSleep() is now called without holding the partition lock, and it no longer re-acquires the partition lock before returning. That makes the wakeup a little cheaper. Once upon a time, re-acquiring the partition lock was ago to prevent a signal handler from longjmping out at a bad time, but nowadays our signal handlers just set flags, and longjmping can only happen at points where we explicitly run CHECK_FOR_INTERRUPTS(). If JoinWaitQueue detects an "early deadlock" before even joining the wait queue, it returns without changing the shared lock entry, leaving the cleanup of the shared lock entry to the caller. This makes early deadlock behave the same as the dontWait=true case. One small side-effect of this refactoring is that we now only set the 'ps' title to say "waiting" when we actually enter the sleep, not when the lock is skipped because dontWait=true, or when a deadlock is detected early before entering the sleep. Based on Thomas Munro's earlier patch and observation that ProcSleep doesn't really need to re-acquire the partition lock. Discussion: https://postgr.es/m/CA%2BhUKGKmO7ze0Z6WXKdrLxmvYa%3DzVGGXOO30MMktufofVwEm1A%40mail.gmail.com --- src/backend/storage/lmgr/lock.c | 182 ++++++++++++++++---------------- src/backend/storage/lmgr/proc.c | 109 +++++++++++-------- src/include/storage/proc.h | 6 +- 3 files changed, 157 insertions(+), 140 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index fc77c88747..ede791c9ff 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -360,8 +360,7 @@ static PROCLOCK *SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc, static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner); static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode); static void FinishStrongLockAcquire(void); -static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, - bool dontWait); +static ProcWaitStatus WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner); static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock); static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent); static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode, @@ -1046,85 +1045,105 @@ LockAcquireExtended(const LOCKTAG *locktag, } else { - /* - * Sleep till someone wakes me up. We do this even in the dontWait - * case, because while trying to go to sleep, we may discover that we - * can acquire the lock immediately after all. - */ - WaitOnLock(locallock, owner, dontWait); + ProcWaitStatus waitResult; /* - * NOTE: do not do any material change of state between here and - * return. All required changes in locktable state must have been - * done when the lock was granted to us --- see notes in WaitOnLock. + * Join the lock's wait queue. We do this even in the dontWait case, + * because while joining the queue, we may discover that we can + * acquire the lock immediately after all. */ + waitResult = JoinWaitQueue(locallock, lockMethodTable, dontWait); - /* - * Check the proclock entry status. If dontWait = true, this is an - * expected case; otherwise, it will only happen if something in the - * ipc communication doesn't work correctly. - */ - if (!(proclock->holdMask & LOCKBIT_ON(lockmode))) + if (waitResult == PROC_WAIT_STATUS_ERROR) { + /* + * We're not getting the lock because a deadlock was detected + * already while trying to join the wait queue, or because we + * would have to wait but the caller requested no blocking. + * + * Undo the changes to shared entries before releasing the + * partition lock. + */ AbortStrongLockAcquire(); + if (proclock->holdMask == 0) + { + uint32 proclock_hashcode; + + proclock_hashcode = ProcLockHashCode(&proclock->tag, + hashcode); + dlist_delete(&proclock->lockLink); + dlist_delete(&proclock->procLink); + if (!hash_search_with_hash_value(LockMethodProcLockHash, + &(proclock->tag), + proclock_hashcode, + HASH_REMOVE, + NULL)) + elog(PANIC, "proclock table corrupted"); + } + else + PROCLOCK_PRINT("LockAcquire: did not join wait queue", proclock); + lock->nRequested--; + lock->requested[lockmode]--; + LOCK_PRINT("LockAcquire: did not join wait queue", + lock, lockmode); + Assert((lock->nRequested > 0) && + (lock->requested[lockmode] >= 0)); + Assert(lock->nGranted <= lock->nRequested); + LWLockRelease(partitionLock); + if (locallock->nLocks == 0) + RemoveLocalLock(locallock); + if (dontWait) { - /* - * We can't acquire the lock immediately. If caller specified - * no blocking, remove useless table entries and return - * LOCKACQUIRE_NOT_AVAIL without waiting. - */ - if (proclock->holdMask == 0) - { - uint32 proclock_hashcode; - - proclock_hashcode = ProcLockHashCode(&proclock->tag, - hashcode); - dlist_delete(&proclock->lockLink); - dlist_delete(&proclock->procLink); - if (!hash_search_with_hash_value(LockMethodProcLockHash, - &(proclock->tag), - proclock_hashcode, - HASH_REMOVE, - NULL)) - elog(PANIC, "proclock table corrupted"); - } - else - PROCLOCK_PRINT("LockAcquire: NOWAIT", proclock); - lock->nRequested--; - lock->requested[lockmode]--; - LOCK_PRINT("LockAcquire: conditional lock failed", - lock, lockmode); - Assert((lock->nRequested > 0) && - (lock->requested[lockmode] >= 0)); - Assert(lock->nGranted <= lock->nRequested); - LWLockRelease(partitionLock); - if (locallock->nLocks == 0) - RemoveLocalLock(locallock); if (locallockp) *locallockp = NULL; return LOCKACQUIRE_NOT_AVAIL; } else + { + DeadLockReport(); + /* DeadLockReport() will not return */ + } + } + + /* + * We are now in the lock queue, or the lock was already granted. If + * queued, go to sleep. + */ + if (waitResult == PROC_WAIT_STATUS_WAITING) + { + Assert(!dontWait); + PROCLOCK_PRINT("LockAcquire: sleeping on lock", proclock); + LOCK_PRINT("LockAcquire: sleeping on lock", lock, lockmode); + LWLockRelease(partitionLock); + + waitResult = WaitOnLock(locallock, owner); + + /* + * NOTE: do not do any material change of state between here and + * return. All required changes in locktable state must have been + * done when the lock was granted to us --- see notes in WaitOnLock. + */ + + if (waitResult == PROC_WAIT_STATUS_ERROR) { /* - * We should have gotten the lock, but somehow that didn't - * happen. If we get here, it's a bug. + * We failed as a result of a deadlock, see CheckDeadLock(). Quit + * now. */ - PROCLOCK_PRINT("LockAcquire: INCONSISTENT", proclock); - LOCK_PRINT("LockAcquire: INCONSISTENT", lock, lockmode); - LWLockRelease(partitionLock); - elog(ERROR, "LockAcquire failed"); + Assert(!dontWait); + DeadLockReport(); + /* DeadLockReport() will not return */ } } - - PROCLOCK_PRINT("LockAcquire: granted", proclock); - LOCK_PRINT("LockAcquire: granted", lock, lockmode); - LWLockRelease(partitionLock); + else + LWLockRelease(partitionLock); + Assert(waitResult == PROC_WAIT_STATUS_OK); } /* The lock was granted to us. Update the local lock entry accordingly */ + Assert((proclock->holdMask & LOCKBIT_ON(lockmode)) != 0); GrantLockLocal(locallock, owner); /* @@ -1796,14 +1815,12 @@ MarkLockClear(LOCALLOCK *locallock) /* * WaitOnLock -- wait to acquire a lock * - * The appropriate partition lock must be held at entry, and will still be - * held at exit. + * This is a wrapper around ProcSleep, with extra tracing and bookkeeping. */ -static void -WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, bool dontWait) +static ProcWaitStatus +WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner) { - LOCKMETHODID lockmethodid = LOCALLOCK_LOCKMETHOD(*locallock); - LockMethod lockMethodTable = LockMethods[lockmethodid]; + ProcWaitStatus result; TRACE_POSTGRESQL_LOCK_WAIT_START(locallock->tag.lock.locktag_field1, locallock->tag.lock.locktag_field2, @@ -1812,12 +1829,13 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, bool dontWait) locallock->tag.lock.locktag_type, locallock->tag.mode); - LOCK_PRINT("WaitOnLock: sleeping on lock", - locallock->lock, locallock->tag.mode); - /* adjust the process title to indicate that it's waiting */ set_ps_display_suffix("waiting"); + /* + * Record the fact that we are waiting for a lock, so that + * LockErrorCleanup will clean up if cancel/die happens. + */ awaitedLock = locallock; awaitedOwner = owner; @@ -1840,28 +1858,7 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, bool dontWait) */ PG_TRY(); { - /* - * If dontWait = true, we handle success and failure in the same way - * here. The caller will be able to sort out what has happened. - */ - if (ProcSleep(locallock, lockMethodTable, dontWait) != PROC_WAIT_STATUS_OK - && !dontWait) - { - - /* - * We failed as a result of a deadlock, see CheckDeadLock(). Quit - * now. - */ - awaitedLock = NULL; - LWLockRelease(LockHashPartitionLock(locallock->hashcode)); - - /* - * Now that we aren't holding the partition lock, we can give an - * error report including details about the detected deadlock. - */ - DeadLockReport(); - /* not reached */ - } + result = ProcSleep(locallock); } PG_CATCH(); { @@ -1883,15 +1880,14 @@ WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner, bool dontWait) /* reset ps display to remove the suffix */ set_ps_display_remove_suffix(); - LOCK_PRINT("WaitOnLock: wakeup on lock", - locallock->lock, locallock->tag.mode); - TRACE_POSTGRESQL_LOCK_WAIT_DONE(locallock->tag.lock.locktag_field1, locallock->tag.lock.locktag_field2, locallock->tag.lock.locktag_field3, locallock->tag.lock.locktag_field4, locallock->tag.lock.locktag_type, locallock->tag.mode); + + return result; } /* diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index f29e02e902..1ec357854e 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1031,7 +1031,7 @@ AuxiliaryPidGetProc(int pid) /* - * ProcSleep -- put a process to sleep on the specified lock + * JoinWaitQueue -- join the wait queue on the specified lock * * It's not actually guaranteed that we need to wait when this function is * called, because it could be that when we try to find a position at which @@ -1040,36 +1040,43 @@ AuxiliaryPidGetProc(int pid) * we get the lock immediately. Because of this, it's sensible for this function * to have a dontWait argument, despite the name. * - * The lock table's partition lock must be held at entry, and will be held - * at exit. + * On entry, the caller has already set up LOCK and PROCLOCK entries to + * reflect that we have "requested" the lock. The caller is responsible for + * cleaning that up, if we end up not joining the queue after all. * - * Result: PROC_WAIT_STATUS_OK if we acquired the lock, PROC_WAIT_STATUS_ERROR - * if not (if dontWait = true, we would have had to wait; if dontWait = false, - * this is a deadlock). + * The lock table's partition lock must be held at entry, and is still held + * at exit. The caller must release it before calling ProcSleep(). * - * ASSUME: that no one will fiddle with the queue until after - * we release the partition lock. + * Result is one of the following: + * + * PROC_WAIT_STATUS_OK - lock was immediately granted + * PROC_WAIT_STATUS_WAITING - joined the wait queue; call ProcSleep() + * PROC_WAIT_STATUS_ERROR - immediate deadlock was detected, or would + * need to wait and dontWait == true * * NOTES: The process queue is now a priority queue for locking. */ ProcWaitStatus -ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) +JoinWaitQueue(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) { LOCKMODE lockmode = locallock->tag.mode; LOCK *lock = locallock->lock; PROCLOCK *proclock = locallock->proclock; uint32 hashcode = locallock->hashcode; - LWLock *partitionLock = LockHashPartitionLock(hashcode); + LWLock *partitionLock PG_USED_FOR_ASSERTS_ONLY = LockHashPartitionLock(hashcode); dclist_head *waitQueue = &lock->waitProcs; PGPROC *insert_before = NULL; LOCKMASK myHeldLocks; - TimestampTz standbyWaitStart = 0; bool early_deadlock = false; - bool allow_autovacuum_cancel = true; - bool logged_recovery_conflict = false; - ProcWaitStatus myWaitStatus; PGPROC *leader = MyProc->lockGroupLeader; + Assert(LWLockHeldByMeInMode(partitionLock, LW_EXCLUSIVE)); + + /* + * Set bitmask of locks this process already holds on this object. + */ + myHeldLocks = MyProc->heldLocks = proclock->holdMask; + /* * Set bitmask of locks this process already holds on this object. */ @@ -1170,6 +1177,13 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) } } + /* + * If we detected deadlock, give up without waiting. This must agree with + * CheckDeadLock's recovery code. + */ + if (early_deadlock) + return PROC_WAIT_STATUS_ERROR; + /* * At this point we know that we'd really need to sleep. If we've been * commanded not to do that, bail out. @@ -1194,31 +1208,43 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) MyProc->waitStatus = PROC_WAIT_STATUS_WAITING; - /* - * If we detected deadlock, give up without waiting. This must agree with - * CheckDeadLock's recovery code. - */ - if (early_deadlock) - { - RemoveFromWaitQueue(MyProc, hashcode); - return PROC_WAIT_STATUS_ERROR; - } + return PROC_WAIT_STATUS_WAITING; +} - /* - * Release the lock table's partition lock. - * - * NOTE: this may also cause us to exit critical-section state, possibly - * allowing a cancel/die interrupt to be accepted. This is OK because we - * have recorded the fact that we are waiting for a lock, and so - * LockErrorCleanup will clean up if cancel/die happens. - */ - LWLockRelease(partitionLock); +/* + * ProcSleep -- put process to sleep waiting on lock + * + * This must be called when JoinWaitQueue() returns PROC_WAIT_STATUS_WAITING. + * Returns after the lock has been granted, or if a deadlock is detected. Can + * also bail out with ereport(ERROR), if some other error condition, or a + * timeout or cancellation is triggered. + * + * Result is one of the following: + * + * PROC_WAIT_STATUS_OK - lock was granted + * PROC_WAIT_STATUS_ERROR - a deadlock was detected + */ +ProcWaitStatus +ProcSleep(LOCALLOCK *locallock) +{ + LOCKMODE lockmode = locallock->tag.mode; + LOCK *lock = locallock->lock; + uint32 hashcode = locallock->hashcode; + LWLock *partitionLock = LockHashPartitionLock(hashcode); + TimestampTz standbyWaitStart = 0; + bool allow_autovacuum_cancel = true; + bool logged_recovery_conflict = false; + ProcWaitStatus myWaitStatus; + + /* The caller must've armed the on-error cleanup mechanism */ + Assert(GetAwaitedLock() == locallock); + Assert(!LWLockHeldByMe(partitionLock)); /* - * Also, now that we will successfully clean up after an ereport, it's - * safe to check to see if there's a buffer pin deadlock against the - * Startup process. Of course, that's only necessary if we're doing Hot - * Standby and are not the Startup process ourselves. + * Now that we will successfully clean up after an ereport, it's safe to + * check to see if there's a buffer pin deadlock against the Startup + * process. Of course, that's only necessary if we're doing Hot Standby + * and are not the Startup process ourselves. */ if (RecoveryInProgress() && !InRecovery) CheckRecoveryConflictDeadlock(); @@ -1627,17 +1653,12 @@ ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable, bool dontWait) standbyWaitStart, GetCurrentTimestamp(), NULL, false); - /* - * Re-acquire the lock table's partition lock, because the caller expects - * us to still hold it. - */ - LWLockAcquire(partitionLock, LW_EXCLUSIVE); - /* * We don't have to do anything else, because the awaker did all the - * necessary update of the lock table and MyProc. + * necessary updates of the lock table and MyProc. (The caller is + * responsible for updating the local lock table.) */ - return MyProc->waitStatus; + return myWaitStatus; } diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 7ddfc8f392..daae929593 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -475,9 +475,9 @@ extern int GetStartupBufferPinWaitBufId(void); extern bool HaveNFreeProcs(int n, int *nfree); extern void ProcReleaseLocks(bool isCommit); -extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, - LockMethod lockMethodTable, - bool dontWait); +extern ProcWaitStatus JoinWaitQueue(LOCALLOCK *locallock, + LockMethod lockMethodTable, bool dontWait); +extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock); extern void ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus); extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock); extern void CheckDeadLockAlert(void); -- 2.34.1 ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Latches vs lwlock contention @ 2024-11-04 16:08 Heikki Linnakangas <[email protected]> parent: Maxim Orlov <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Heikki Linnakangas @ 2024-11-04 16:08 UTC (permalink / raw) To: Maxim Orlov <[email protected]>; +Cc: Thomas Munro <[email protected]>; pgsql-hackers; Yura Sokolov <[email protected]>; Andres Freund <[email protected]> On 10/09/2024 19:53, Maxim Orlov wrote: > I looked at the patch set and found it quite useful. > > The first 7 patches are just refactoring and may be committed separately > if needed. > There were minor problems: patch #5 don't want to apply clearly and the > #8 is complained > about partitionLock is unused if we build without asserts. So, I add a > PG_USED_FOR_ASSERTS_ONLY > to solve the last issue. > > Again, overall patch looks good and seems useful to me. Here is the > rebased v5 version based on Heikki's patch set above. Committed, thanks for the review! In case you're wondering, I committed some of the smaller patches separately, but also squashed some of them with the main patch, On closer look, the first patch, "Remove LOCK_PRINT() call that could point to garbage", wasn't fixing any existing issue. The LOCK_PRINT() was fine, because we held the partition lock. But it became necessary with the main patch, so I squashed it with that. And the others that I squashed were just not that interesting on their own. The rest of Thomas's SetLatches work remains, so I left the commitfest entry in "Needs review" state. -- Heikki Linnakangas Neon (https://neon.tech) ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Latches vs lwlock contention @ 2025-03-27 05:00 Alexander Lakhin <[email protected]> parent: Heikki Linnakangas <[email protected]> 0 siblings, 1 reply; 7+ messages in thread From: Alexander Lakhin @ 2025-03-27 05:00 UTC (permalink / raw) To: Heikki Linnakangas <[email protected]>; Maxim Orlov <[email protected]>; +Cc: Thomas Munro <[email protected]>; pgsql-hackers; Yura Sokolov <[email protected]>; Andres Freund <[email protected]> Hello Heikki, 04.11.2024 18:08, Heikki Linnakangas wrote: > Committed, thanks for the review! I've discovered that the following script: export PGOPTIONS='-c lock_timeout=1s' createdb regression for i in {1..100}; do echo "ITERATION: $i" psql -c "CREATE TABLE t(i int);" cat << 'EOF' | psql & DO $$ DECLARE i int; BEGIN FOR i IN 1 .. 5000000 LOOP INSERT INTO t VALUES (1); END LOOP; END; $$; EOF sleep 1 psql -c "DROP TABLE t" & cat << 'EOF' | psql & COPY t FROM STDIN; 0 \. EOF wait psql -c "DROP TABLE t" || break; done causes a segmentation fault on master (it fails on iterations 5, 4, 26 for me): ITERATION: 26 CREATE TABLE ERROR: canceling statement due to lock timeout ERROR: canceling statement due to lock timeout invalid command \. ERROR: syntax error at or near "0" LINE 1: 0 ^ server closed the connection unexpectedly Core was generated by `postgres: law regression [local] idle '. Program terminated with signal SIGSEGV, Segmentation fault. #0 GrantLockLocal (locallock=0x5a1d75c35ba8, owner=0x5a1d75c18630) at lock.c:1805 1805 lockOwners[i].owner = owner; (gdb) bt #0 GrantLockLocal (locallock=0x5a1d75c35ba8, owner=0x5a1d75c18630) at lock.c:1805 #1 0x00005a1d51e93ee7 in GrantAwaitedLock () at lock.c:1887 #2 0x00005a1d51ea1e58 in LockErrorCleanup () at proc.c:814 #3 0x00005a1d51b9a1a7 in AbortTransaction () at xact.c:2853 #4 0x00005a1d51b9abc6 in AbortCurrentTransactionInternal () at xact.c:3579 #5 AbortCurrentTransaction () at xact.c:3457 #6 0x00005a1d51eafeda in PostgresMain (dbname=<optimized out>, username=0x5a1d75c139b8 "law") at postgres.c:4440 (gdb) p lockOwners $1 = (LOCALLOCKOWNER *) 0x0 git bisect led me to 3c0fd64fe. Could you please take a look? Best regards, Alexander Lakhin Neon (https://neon.tech) ^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Latches vs lwlock contention @ 2025-03-28 18:20 Heikki Linnakangas <[email protected]> parent: Alexander Lakhin <[email protected]> 0 siblings, 0 replies; 7+ messages in thread From: Heikki Linnakangas @ 2025-03-28 18:20 UTC (permalink / raw) To: Alexander Lakhin <[email protected]>; Maxim Orlov <[email protected]>; +Cc: Thomas Munro <[email protected]>; pgsql-hackers; Yura Sokolov <[email protected]>; Andres Freund <[email protected]> On 27/03/2025 07:00, Alexander Lakhin wrote: > I've discovered that the following script: > export PGOPTIONS='-c lock_timeout=1s' > createdb regression > for i in {1..100}; do > echo "ITERATION: $i" > psql -c "CREATE TABLE t(i int);" > cat << 'EOF' | psql & > DO $$ > DECLARE > i int; > BEGIN > FOR i IN 1 .. 5000000 LOOP > INSERT INTO t VALUES (1); > END LOOP; > END; > $$; > EOF > sleep 1 > psql -c "DROP TABLE t" & > cat << 'EOF' | psql & > COPY t FROM STDIN; > 0 > \. > EOF > wait > > psql -c "DROP TABLE t" || break; > done > > causes a segmentation fault on master (it fails on iterations 5, 4, 26 > for me): > ITERATION: 26 > CREATE TABLE > ERROR: canceling statement due to lock timeout > ERROR: canceling statement due to lock timeout > invalid command \. > ERROR: syntax error at or near "0" > LINE 1: 0 > ^ > server closed the connection unexpectedly > > Core was generated by `postgres: law regression [local] > idle '. > Program terminated with signal SIGSEGV, Segmentation fault. > #0 GrantLockLocal (locallock=0x5a1d75c35ba8, owner=0x5a1d75c18630) at > lock.c:1805 > 1805 lockOwners[i].owner = owner; > (gdb) bt > #0 GrantLockLocal (locallock=0x5a1d75c35ba8, owner=0x5a1d75c18630) at > lock.c:1805 > #1 0x00005a1d51e93ee7 in GrantAwaitedLock () at lock.c:1887 > #2 0x00005a1d51ea1e58 in LockErrorCleanup () at proc.c:814 > #3 0x00005a1d51b9a1a7 in AbortTransaction () at xact.c:2853 > #4 0x00005a1d51b9abc6 in AbortCurrentTransactionInternal () at xact.c:3579 > #5 AbortCurrentTransaction () at xact.c:3457 > #6 0x00005a1d51eafeda in PostgresMain (dbname=<optimized out>, > username=0x5a1d75c139b8 "law") at postgres.c:4440 > > (gdb) p lockOwners > $1 = (LOCALLOCKOWNER *) 0x0 > > git bisect led me to 3c0fd64fe. > Could you please take a look? Great, thanks for the repro! With that, I was able to capture the failure with 'rr' and understand what happens: Commit 3c0fd64fe removed "lockAwaited = NULL;" from LockErrorCleanup(). Because of that, if the lock had been granted to us, and if LockErrorCleanup() was called twice, the second call would call GrantAwaitedLock() even if the lock was already released and cleaned up. I've pushed a fix to put that back. -- Heikki Linnakangas Neon (https://neon.tech) ^ permalink raw reply [nested|flat] 7+ messages in thread
end of thread, other threads:[~2025-03-28 18:20 UTC | newest] Thread overview: 7+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2020-06-06 22:42 [PATCH v10 1/3] Allow CREATE INDEX CONCURRENTLY on partitioned table Justin Pryzby <[email protected]> 2022-10-28 03:56 Latches vs lwlock contention Thomas Munro <[email protected]> 2022-11-01 11:09 ` Re: Latches vs lwlock contention Thomas Munro <[email protected]> 2024-09-10 16:53 ` Re: Latches vs lwlock contention Maxim Orlov <[email protected]> 2024-11-04 16:08 ` Re: Latches vs lwlock contention Heikki Linnakangas <[email protected]> 2025-03-27 05:00 ` Re: Latches vs lwlock contention Alexander Lakhin <[email protected]> 2025-03-28 18:20 ` Re: Latches vs lwlock contention Heikki Linnakangas <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox