From c91fcee144f86be2881eb48f215d26fcfa459c2d Mon Sep 17 00:00:00 2001
From: Gavin Panella <gavinpanella@gmail.com>
Date: Sun, 10 Aug 2025 19:30:19 +0200
Subject: [PATCH v1] When getting EINVAL from semget(2), probe for EEXIST

---
 src/backend/port/sysv_sema.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/src/backend/port/sysv_sema.c b/src/backend/port/sysv_sema.c
index 423b2b4f9d6..51308a651cf 100644
--- a/src/backend/port/sysv_sema.c
+++ b/src/backend/port/sysv_sema.c
@@ -113,6 +113,41 @@ InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey, int numSems)
 			)
 			return -1;
 
+		/*
+		 * Typically EINVAL means that the number of semaphores requested is
+		 * greater than the system imposed maximum per set, or that the set
+		 * exists but that it has fewer than numSems semaphores in it. In the
+		 * latter case, presumably the set was allocated by another program or
+		 * perhaps by another version of PostgreSQL (SEMAS_PER_SET was raised
+		 * in version 17, for example).
+		 *
+		 * On many platforms, including Linux, since the flags passed to
+		 * semget(2) includes IPC_CREAT | IPC_EXCL, it would return <0 and set
+		 * errno to EEXIST when numSems is greater than the number of
+		 * semaphores in a preexisting set.
+		 *
+		 * On some platforms, e.g. macOS 15.6, when encountering an existing
+		 * set, it appears that numSems is checked first, before considering
+		 * flags. This means that when numSems is greater than the number of
+		 * semaphores in a preexisting set, semget(2) returns <0 and errno is
+		 * set to EINVAL. Hence, on these platforms, EINVAL can mean we've hit
+		 * a system limit and thus we cannot continue, OR it can mean we've
+		 * found an existing set, a benign and anticipated situation; see how
+		 * EEXIST is handled above.
+		 *
+		 * Fortunately we can distinguish these by calling semget(2) again,
+		 * asking for _zero_ semaphores. There is a potential race, e.g. the
+		 * semaphore set could be removed, but if we take the stance that we
+		 * will not use this set in any case, we need only check that errno is
+		 * _not_ EINVAL this time.
+		 */
+		if (saved_errno == EINVAL)
+		{
+			semId = semget(semKey, 0, IPC_CREAT | IPC_EXCL | IPCProtection);
+			if (semId >= 0 || errno != EINVAL)
+				return -1;
+		}
+
 		/*
 		 * Else complain and abort
 		 */
-- 
2.50.1

