From e30aab2276b05212dbfe23d2a28636036610645d Mon Sep 17 00:00:00 2001 From: Thomas Munro 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)