agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH v1 4/8] Add jsonpath array constructors 586+ messages / 2 participants [nested] [flat]
* [PATCH v1 4/8] Add jsonpath array constructors @ 2017-04-06 19:36 Nikita Glukhov <n.gluhov@postgrespro.ru> 0 siblings, 0 replies; 586+ messages in thread From: Nikita Glukhov @ 2017-04-06 19:36 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 7 ++++ src/backend/utils/adt/jsonpath.c | 30 ++++++++++++--- src/backend/utils/adt/jsonpath_exec.c | 19 ++++++++++ src/backend/utils/adt/jsonpath_gram.y | 2 + src/include/utils/jsonpath.h | 1 + src/test/regress/expected/jsonb_jsonpath.out | 57 ++++++++++++++++++++++++++++ src/test/regress/expected/jsonpath.out | 12 ++++++ src/test/regress/sql/jsonb_jsonpath.sql | 11 ++++++ src/test/regress/sql/jsonpath.sql | 3 ++ 9 files changed, 136 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9defd1a..7347e71 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -12632,6 +12632,13 @@ table2-mapping <entry><literal>pg $[*], 4, 5</literal></entry> <entry><literal>1, 2, 3, 4, 5</literal></entry> </row> + <row> + <entry>Array constructor</entry> + <entry>Construct a JSON array by enumeration of its elements enclosed in brackets</entry> + <entry><literal>[1, 2, 3]</literal></entry> + <entry><literal>pg [$[*], 4, 5]</literal></entry> + <entry><literal>[1, 2, 3, 4, 5]</literal></entry> + </row> </tbody> </tgroup> </table> diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 0fb4257..16031ba 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -368,13 +368,19 @@ flattenJsonPathParseItem(JsonPathEncodingContext *cxt, JsonPathParseItem *item, case jpiMinus: case jpiExists: case jpiDatetime: + case jpiArray: { int32 arg = reserveSpaceForItemPointer(buf); - chld = !item->value.arg ? pos : - flattenJsonPathParseItem(cxt, item->value.arg, - nestingLevel + argNestingLevel, - insideArraySubscript); + if (item->type == jpiArray) + checkJsonPathExtensionsEnabled(cxt, item->type); + + if (!item->value.arg) + break; + + chld = flattenJsonPathParseItem(cxt, item->value.arg, + nestingLevel + argNestingLevel, + insideArraySubscript); *(int32 *) (buf->data + arg) = chld - pos; } break; @@ -780,6 +786,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, if (printBracketes || jspHasNext(v)) appendStringInfoChar(buf, ')'); break; + case jpiArray: + appendStringInfoChar(buf, '['); + if (v->content.arg) + { + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ']'); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -979,6 +994,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiMinus: case jpiFilter: case jpiDatetime: + case jpiArray: read_int32(v->content.arg, base, pos); break; case jpiIndexArray: @@ -1009,7 +1025,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiExists || v->type == jpiPlus || v->type == jpiMinus || - v->type == jpiDatetime); + v->type == jpiDatetime || + v->type == jpiArray); jspInitByBuffer(a, v->base, v->content.arg); } @@ -1060,7 +1077,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiDatetime || v->type == jpiKeyValue || v->type == jpiStartsWith || - v->type == jpiSequence); + v->type == jpiSequence || + v->type == jpiArray); if (a) jspInitByBuffer(a, v->base, v->nextPos); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 1e31d3c..1dee30a 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1175,6 +1175,25 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; } + case jpiArray: + { + JsonValueList list = {0}; + JsonbValue *arr; + + if (jsp->content.arg) + { + jspGetArg(jsp, &elem); + res = executeItem(cxt, &elem, jb, &list); + + if (jperIsError(res)) + break; + } + + arr = wrapItemsInArray(&list); + res = executeNextItem(cxt, jsp, NULL, arr, found, false); + break; + } + default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 1de6f5a..3507c1f 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -217,6 +217,8 @@ path_primary: | '@' { $$ = makeItemType(jpiCurrent); } | LAST_P { $$ = makeItemType(jpiLast); } | '(' expr_seq ')' { $$ = $2; } + | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); } + | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); } ; accessor_expr: diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 4cbdce3..1b2b964 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -88,6 +88,7 @@ typedef enum JsonPathItemType jpiStartsWith, /* STARTS WITH predicate */ jpiLikeRegex, /* LIKE_REGEX predicate */ jpiSequence, /* sequence constructor: 'expr, ...' */ + jpiArray, /* array constructor: '[expr, ...]' */ } JsonPathItemType; /* XQuery regex mode flags for LIKE_REGEX predicate */ diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index c5252ad..cd56876 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2199,6 +2199,12 @@ SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)'); ------------------ (0 rows) +SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', 'pg [$[*].a]'); + jsonb_path_query +------------------ + [1, 2] +(1 row) + SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); ERROR: JSON object does not contain key "a" SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a'); @@ -2231,6 +2237,12 @@ SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*]. [] (1 row) +SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', 'pg [$[*].a]'); + jsonb_path_query_array +------------------------ + [[1, 2]] +(1 row) + SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); ERROR: JSON object does not contain key "a" SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true); @@ -2578,3 +2590,48 @@ select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 5) ? (@ == 3)]'); select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 3) ? (@ == 3)]'); ERROR: jsonpath array subscript is not a single numeric value +-- extension: array constructors +select jsonb_path_query('[1, 2, 3]', 'pg []'); + jsonb_path_query +------------------ + [] +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'pg [1, 2, $[*], 4, 5]'); + jsonb_path_query +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'pg [1, 2, $[*], 4, 5][*]'); + jsonb_path_query +------------------ + 1 + 2 + 1 + 2 + 3 + 4 + 5 +(7 rows) + +select jsonb_path_query('[1, 2, 3]', 'pg [(1, (2, $[*])), (4, 5)]'); + jsonb_path_query +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'pg [[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'); + jsonb_path_query +------------------------------- + [[1, 2], [1, 2, 3, 4], 5, []] +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'pg strict [1, 2, $[*].a, 4, 5]'); +ERROR: jsonpath member accessor can only be applied to an object +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'pg [$[*][*] ? (@ > 3)]'); + jsonb_path_query +------------------ + [4, 5, 6, 7] +(1 row) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index f6a132c..e212deb 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -604,6 +604,18 @@ select 'pg $[(1, (2, $.a)), 3, (4, 5)]'::jsonpath; pg $[(1, (2, $."a")),3,(4, 5)] (1 row) +select 'pg []'::jsonpath; + jsonpath +---------- + pg [] +(1 row) + +select 'pg [[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath; + jsonpath +--------------------------------------------- + pg [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]] +(1 row) + select '$ ? (@.a < 1)'::jsonpath; jsonpath --------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 0619ade..be5c7c8 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -525,6 +525,7 @@ set time zone default; SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]'); SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)'); +SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', 'pg [$[*].a]'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a'); @@ -532,6 +533,7 @@ SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 1, "max": 4}'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 3, "max": 4}'); +SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', 'pg [$[*].a]'); SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true); @@ -587,3 +589,12 @@ select jsonb_path_query('[1,2,3,4,5]', 'pg -(10, 20, $[1 to 3], 30)'); select jsonb_path_query('[1,2,3,4,5]', 'pg lax (10, 20.5, $[1 to 3], "30").double()'); select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 5) ? (@ == 3)]'); select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 3) ? (@ == 3)]'); + +-- extension: array constructors +select jsonb_path_query('[1, 2, 3]', 'pg []'); +select jsonb_path_query('[1, 2, 3]', 'pg [1, 2, $[*], 4, 5]'); +select jsonb_path_query('[1, 2, 3]', 'pg [1, 2, $[*], 4, 5][*]'); +select jsonb_path_query('[1, 2, 3]', 'pg [(1, (2, $[*])), (4, 5)]'); +select jsonb_path_query('[1, 2, 3]', 'pg [[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'); +select jsonb_path_query('[1, 2, 3]', 'pg strict [1, 2, $[*].a, 4, 5]'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'pg [$[*][*] ? (@ > 3)]'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 2a53168..23ae376 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -113,6 +113,9 @@ select 'pg (1, 2, $.a) == 5'::jsonpath; select 'pg $[(1, 2, $.a) to (3, 4)]'::jsonpath; select 'pg $[(1, (2, $.a)), 3, (4, 5)]'::jsonpath; +select 'pg []'::jsonpath; +select 'pg [[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath; + select '$ ? (@.a < 1)'::jsonpath; select '$ ? (@.a < -1)'::jsonpath; select '$ ? (@.a < +1)'::jsonpath; -- 2.7.4 --------------574DCE16CAD28E71E2083DCD Content-Type: text/x-patch; name="v1-0005-Add-jsonpath-object-constructors.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="v1-0005-Add-jsonpath-object-constructors.patch" ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Xf36GNcW+0xZNily Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
* [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic @ 2026-07-09 19:20 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 586+ messages in thread From: Nathan Bossart @ 2026-07-09 19:20 UTC (permalink / raw) --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..17e98c9efdc 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ int nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -598,7 +581,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = min - SIG_THRESHOLD; lowbound = min - MAXNUMMESSAGES + minFree; @@ -644,7 +627,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -653,7 +636,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.50.1 (Apple Git-155) --Ot5x3ffkWEuxilWO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-convert-ParallelBitmapHeapState-state-to-an-atomi.patch ^ permalink raw reply [nested|flat] 586+ messages in thread
end of thread, other threads:[~2026-07-09 19:20 UTC | newest] Thread overview: 586+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2017-04-06 19:36 [PATCH v1 4/8] Add jsonpath array constructors Nikita Glukhov <n.gluhov@postgrespro.ru> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v2 1/9] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org> 2026-07-09 19:20 [PATCH v1 1/8] convert SISeg->maxMsgNum to an atomic Nathan Bossart <nathan@postgresql.org>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox