Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x96VW-00000001kAh-3sGn for pgsql-hackers@arkaria.postgresql.org; Tue, 22 Sep 2026 19:50:27 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.98.2) (envelope-from ) id 1x96VW-000000013qH-0t0z for pgsql-hackers@arkaria.postgresql.org; Tue, 22 Sep 2026 19:50:26 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x96VV-000000013q9-2v8g for pgsql-hackers@lists.postgresql.org; Tue, 22 Sep 2026 19:50:25 +0000 Received: from mail-qv2-x1d.google.com ([2607:f8b0:4864:33::1d]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1x96VS-00000000izX-3Nji for pgsql-hackers@postgresql.org; Tue, 22 Sep 2026 19:50:24 +0000 Received: by mail-qv2-x1d.google.com with SMTP id 6a1803df08f44-90cdfcb5cb3so1868296d6.3 for ; Tue, 22 Sep 2026 12:50:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790106623; x=1790711423; darn=postgresql.org; h=in-reply-to:content-disposition:content-type:mime-version :references:message-id:subject:cc:to:from:date:from:to:cc:subject :date:message-id:reply-to:content-type; bh=U8tLZPm+NbCII+4ENTmYP6Wu4dFxxdDaOFNiAHdJZWQ=; b=tBg9W8Rdnslh2+XCBXF7IP6S/5LTOcpXfysnAyhLpWyF1MQE2RMCCSOdMLGrYsOfvY BfQW50CJ+McA9rvwl1XHKpgqFwgTyI0OyXfme8lUwbcL2oKGsHV70S/ZsU1Y4rgmG4Lc 1b68NxcqWYE8p/iXZm7YEftpwurmceR0XtmsrNm+C40hMOmO9r53r6pk+1UiuXkzuoSA azw+bUwWCAbK7lSUbrxez057BCC2uYQ9JqnfkNEHbpA/naPHCq2d7z7nP1TmCim5xqo1 TOQHJZN7McPefPS5gSOAKmPEf1jYYtQPfMPsNL+03XJAKLbD7k0mUQrFeV1Kfw7vTbLm W77Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790106623; x=1790711423; h=in-reply-to:content-disposition:content-type:mime-version :references:message-id:subject:cc:to:from:date:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=U8tLZPm+NbCII+4ENTmYP6Wu4dFxxdDaOFNiAHdJZWQ=; b=LnEPsDTJUO5XgSV4OPoREKQFKu0tH3REFoH6tThQNj66ibcRLfanlSwcHPfAaPZseB iZCw5DHbmecr5gX2eoyKS6y2BOG77cSsF4o8ss03W9tmrV35FcLdurV3OBICPzL4aHDK th35g5r7az/9kTQVW8ejGTTQdkKnmeS/LEExk2kNaMbeUbnhKhzgtAbBA58rdPctEUpG 8O3EfC4U8xjn4ayPKiGW3ABDzvBGV0JHPqHIG2k6Mh0euicCsK44LzaSQ0VfR9HWrbfP GxaZHt4aiyTYkPWnsjnVo3X8CMiuhA5d827eXA4OiKzngeFO8Abv+dZV/irbe3JbRyBF i3mg== X-Forwarded-Encrypted: i=1; AKwUvBwBPIyjTgu+qTkHD2ofiNPgkDlAxD7zyf7KC8WKeF02+mqQWslxXNC30uQMtPWi+ZIbeEjnX+dXJ7+kfWtA@postgresql.org X-Gm-Message-State: AFuF++mCW+tOhWEiVBawwS156s5oBzNjJYkoUTByFCPxHANHmEdhGr3X 8muA8c4JRT/FglzQtf1EgypsOxp7UkjX7VmQa3YIoHdnhodO7rUZR/ly X-Gm-Gg: AYBFou1V1ea9iDuYvSI4jEHphcVhLF0mKpqTKeAL637LS6IbQqGLGjjvKM2dUyjei0T swqOQnD0ScHOIho8PjtkxVImXalMAQ3DCMvOOxZq5GgkgKF97hdYg0Lc2gvoUq85MOca505R0Nj Fq+UT/YCUNk2yd1n6+cNg5CqcQXumDjWUygAON7hG+FAaGrIOmns/aeADbB71Px6BjZsDGS1JEi O3C+aWsrB7B2R8HS1pJKN5VSSBgwG61wwtN955JgGOmNrr0KUZlkVQhkypFWEiQDdMk4P5A88K/ WAtnRqeb3+yv1m7LcKj6+mqeO01gL33HQuwUaY/Hy9Q/+ZhFX7vEDsGPi9MU+251HQP7b5uXd3I qeg05M3y2cdy3ckizuY23DonHYtyuzBzvidRYnT6zdh2ew7LYlec3waLLO2Ew2mDmh6kpVCNIyH WO/wVvlSaWP1RCaIIGXzvDcASaPpb0JjeGFg/LQZl1nGnWk1HC6HjrbrhSDL7S1uVkzuOrqQ/kD skjHHxgbIaXGTTE7zYNwCMLrNulzu5LAEkVwgmuCRAxaM11/LD+ksjr7lFg9LhvtmJlkE3bnk8x C2xsgG+u20SuuCofHAeIgtM+j4JfdJzAzIb2 X-Received: by 2002:a05:620a:3187:b0:93b:d7a1:ba17 with SMTP id af79cd13be357-93c25260e5emr71288985a.66.1790106622677; Tue, 22 Sep 2026 12:50:22 -0700 (PDT) Received: from nathan (162-195-168-172.lightspeed.stlsmo.sbcglobal.net. [162.195.168.172]) by smtp.gmail.com with ESMTPSA id af79cd13be357-93c24823ccfsm52216185a.11.2026.09.22.12.50.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 22 Sep 2026 12:50:21 -0700 (PDT) Date: Tue, 22 Sep 2026 14:50:19 -0500 From: Nathan Bossart To: Heikki Linnakangas Cc: Andres Freund , Peter Eisentraut , pgsql-hackers@postgresql.org Subject: Re: convert various variables to atomics Message-ID: References: <9d8c317d-d933-46c7-b675-4b9308eaca2b@eisentraut.org> <207c0bfb-6e06-4358-bb2f-c961915efc36@eisentraut.org> <3856d1cf-53a8-414b-98d9-829d5a455a86@iki.fi> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="ARdL8blrvHVCiCuh" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --ARdL8blrvHVCiCuh Content-Type: text/plain; charset=us-ascii Content-Disposition: inline I've now committed everything except for these last two patches, which I'm planning to commit tomorrow. -- nathan --ARdL8blrvHVCiCuh Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v4-0001-Use-unsigned-integers-for-sinval-message-numbers.patch From 8438305333b38a31c101f0051cc8b86aaec1e087 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Tue, 22 Sep 2026 14:31:14 -0500 Subject: [PATCH v4 1/2] Use unsigned integers for sinval message numbers. Currently, the message numbers in sinvaladt.c are ints, but they are never negative, and the code already takes pains to keep them from overflowing. This commit changes them to uint32. The only wrinkle is that SICleanupQueue() computes two thresholds by subtracting from maxMsgNum, and those could previously go negative. They are now clamped at zero, which disables the corresponding checks just as a negative threshold did. This is preparatory work for a follow-up commit that will convert maxMsgNum to an unsigned atomic variable. Author: Yura Sokolov Reviewed-by: Heikki Linnakangas Reviewed-by: Peter Eisentraut Reviewed-by: Andres Freund Discussion: https://postgr.es/m/30aa0030-f694-44ef-a19d-6ef7ddb69374%40postgrespro.ru Discussion: https://postgr.es/m/alAJeRRzehDjLaF1%40nathan --- src/backend/storage/ipc/sinvaladt.c | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 37a21ffaf1a..b29b4bcc5be 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -93,7 +93,7 @@ * read maxMsgNum if you are not holding SInvalWriteLock, and you need the * spinlock to write maxMsgNum unless you are holding both locks.) * - * Note: since maxMsgNum is an int and hence presumably atomically readable/ + * Note: since maxMsgNum is a uint32 and hence presumably atomically readable/ * writable, the spinlock might seem unnecessary. The reason it is needed * is to provide a memory barrier: we need to be sure that messages written * to the array are actually there before maxMsgNum is increased, and that @@ -140,7 +140,7 @@ typedef struct ProcState /* procPid is zero in an inactive ProcState array entry. */ pid_t procPid; /* PID of backend, for signaling */ /* nextMsgNum is meaningless if procPid == 0 or resetState is true. */ - int nextMsgNum; /* next message number to read */ + uint32 nextMsgNum; /* next message number to read */ bool resetState; /* backend needs to reset its state */ bool signaled; /* backend has been sent catchup signal */ bool hasMessages; /* backend has unread messages */ @@ -168,9 +168,9 @@ typedef struct SISeg /* * General state information */ - int minMsgNum; /* oldest message still needed */ - int maxMsgNum; /* next message number to be assigned */ - int nextThreshold; /* # of messages to call SICleanupQueue */ + uint32 minMsgNum; /* oldest message still needed */ + uint32 maxMsgNum; /* next message number to be assigned */ + uint32 nextThreshold; /* # of messages to call SICleanupQueue */ slock_t msgnumLock; /* spinlock protecting maxMsgNum */ @@ -385,8 +385,8 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) while (n > 0) { int nthistime = Min(n, WRITE_QUANTUM); - int numMsgs; - int max; + uint32 numMsgs; + uint32 max; int i; n -= nthistime; @@ -476,7 +476,7 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) { SISeg *segP; ProcState *stateP; - int max; + uint32 max; int n; segP = shmInvalBuffer; @@ -579,11 +579,11 @@ void SICleanupQueue(bool callerHasWriteLock, int minFree) { SISeg *segP = shmInvalBuffer; - int min, + uint32 min, minsig, lowbound, - numMsgs, - i; + numMsgs; + int i; ProcState *needSig = NULL; /* Lock out all writers and readers */ @@ -597,15 +597,19 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * backends that are too far back. Note that because we ignore sendOnly * backends here it is possible for them to keep sending messages without * a problem even when they are the only active backend. + * + * Note that the thresholds are clamped at zero rather than allowed to + * wrap around. */ min = segP->maxMsgNum; - minsig = min - SIG_THRESHOLD; - lowbound = min - MAXNUMMESSAGES + minFree; + minsig = (min > SIG_THRESHOLD) ? min - SIG_THRESHOLD : 0; + lowbound = (min + minFree > MAXNUMMESSAGES) ? + min + minFree - MAXNUMMESSAGES : 0; for (i = 0; i < segP->numProcs; i++) { ProcState *stateP = &segP->procState[segP->pgprocnos[i]]; - int n = stateP->nextMsgNum; + uint32 n = stateP->nextMsgNum; /* Ignore if already in reset state */ Assert(stateP->procPid != 0); -- 2.55.0 --ARdL8blrvHVCiCuh Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v4-0002-Convert-SISeg-maxMsgNum-to-an-atomic-variable.patch From 9903d1cdc01f12acb510ffa8f22ddb1048ec1f48 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Tue, 22 Sep 2026 14:36:42 -0500 Subject: [PATCH v4 2/2] Convert SISeg->maxMsgNum to an atomic variable. Currently, this variable is a uint32 protected by a spinlock. The spinlock exists only to provide memory barriers, so by converting the variable to an atomic and using the barrier-providing accessors in the spinlock's place, we can remove the spinlock. Author: Yura Sokolov Reviewed-by: Heikki Linnakangas Reviewed-by: Peter Eisentraut Reviewed-by: Andres Freund Reviewed-by: Zsolt Parragi Tested-by: solai v Discussion: https://postgr.es/m/30aa0030-f694-44ef-a19d-6ef7ddb69374%40postgrespro.ru Discussion: https://postgr.es/m/alAJeRRzehDjLaF1%40nathan --- src/backend/storage/ipc/sinvaladt.c | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index b29b4bcc5be..bc5f9537710 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -24,7 +24,6 @@ #include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" -#include "storage/spin.h" #include "storage/subsystems.h" /* @@ -87,19 +86,10 @@ * has no need to touch anyone's ProcState, except in the infrequent cases * when SICleanupQueue is needed. The only point of overlap is that * the writer wants to change maxMsgNum while readers need to read it. - * We deal with that by having a spinlock that readers must take for just - * long enough to read maxMsgNum, while writers take it for just long enough - * to write maxMsgNum. (The exact rule is that you need the spinlock to - * read maxMsgNum if you are not holding SInvalWriteLock, and you need the - * spinlock to write maxMsgNum unless you are holding both locks.) - * - * Note: since maxMsgNum is a uint32 and hence presumably atomically readable/ - * writable, the spinlock might seem unnecessary. The reason it is needed - * is to provide a memory barrier: we need to be sure that messages written - * to the array are actually there before maxMsgNum is increased, and that - * readers will see that data after fetching maxMsgNum. Multiprocessors - * that have weak memory-ordering guarantees can fail without the memory - * barrier instructions that are included in the spinlock sequences. + * We deal with that by making maxMsgNum an atomic variable. (The exact rule + * is that you need to use a barrier-providing accessor to read maxMsgNum if + * you are not holding SInvalWriteLock, and you need a barrier-providing + * accessor to write maxMsgNum unless you are holding both locks.) */ @@ -169,11 +159,9 @@ typedef struct SISeg * General state information */ uint32 minMsgNum; /* oldest message still needed */ - uint32 maxMsgNum; /* next message number to be assigned */ + pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */ uint32 nextThreshold; /* # of messages to call SICleanupQueue */ - slock_t msgnumLock; /* spinlock protecting maxMsgNum */ - /* * Circular buffer holding shared-inval messages */ @@ -244,11 +232,10 @@ SharedInvalShmemInit(void *arg) { int i; - /* Clear message counters, init spinlock */ + /* Clear message counters */ shmInvalBuffer->minMsgNum = 0; - shmInvalBuffer->maxMsgNum = 0; + pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0); shmInvalBuffer->nextThreshold = CLEANUP_MIN; - SpinLockInit(&shmInvalBuffer->msgnumLock); /* The buffer[] array is initially all unused, so we need not fill it */ @@ -306,7 +293,7 @@ SharedInvalBackendInit(bool sendOnly) /* mark myself active, with all extant messages already read */ stateP->procPid = MyProcPid; - stateP->nextMsgNum = segP->maxMsgNum; + stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum); stateP->resetState = false; stateP->signaled = false; stateP->hasMessages = false; @@ -402,7 +389,7 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) */ for (;;) { - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs + nthistime > MAXNUMMESSAGES || numMsgs >= segP->nextThreshold) SICleanupQueue(true, nthistime); @@ -413,17 +400,15 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n) /* * Insert new message(s) into proper slot of circular buffer */ - max = segP->maxMsgNum; + max = pg_atomic_read_u32(&segP->maxMsgNum); while (nthistime-- > 0) { segP->buffer[max % MAXNUMMESSAGES] = *data++; max++; } - /* Update current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - segP->maxMsgNum = max; - SpinLockRelease(&segP->msgnumLock); + /* Update current value of maxMsgNum using barrier */ + pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max); /* * Now that the maxMsgNum change is globally visible, we give everyone @@ -509,10 +494,8 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) */ stateP->hasMessages = false; - /* Fetch current value of maxMsgNum using spinlock */ - SpinLockAcquire(&segP->msgnumLock); - max = segP->maxMsgNum; - SpinLockRelease(&segP->msgnumLock); + /* Fetch current value of maxMsgNum using barrier */ + max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum); if (stateP->resetState) { @@ -601,7 +584,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Note that the thresholds are clamped at zero rather than allowed to * wrap around. */ - min = segP->maxMsgNum; + min = pg_atomic_read_u32(&segP->maxMsgNum); minsig = (min > SIG_THRESHOLD) ? min - SIG_THRESHOLD : 0; lowbound = (min + minFree > MAXNUMMESSAGES) ? min + minFree - MAXNUMMESSAGES : 0; @@ -648,7 +631,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) if (min >= MSGNUMWRAPAROUND) { segP->minMsgNum -= MSGNUMWRAPAROUND; - segP->maxMsgNum -= MSGNUMWRAPAROUND; + pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND); for (i = 0; i < segP->numProcs; i++) segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND; } @@ -657,7 +640,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) * Determine how many messages are still in the queue, and set the * threshold at which we should repeat SICleanupQueue(). */ - numMsgs = segP->maxMsgNum - segP->minMsgNum; + numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum; if (numMsgs < CLEANUP_MIN) segP->nextThreshold = CLEANUP_MIN; else -- 2.55.0 --ARdL8blrvHVCiCuh--