Skip to content

Commit 21fddb3

Browse files
committed
Don't treat EINVAL from semget() as a hard failure.
It turns out that on some platforms (at least current macOS, NetBSD, OpenBSD) semget(2) will return EINVAL if there is a pre-existing semaphore set with the same key and too few semaphores. Our code expects EEXIST in that case and treats EINVAL as a hard failure, resulting in failure during initdb or postmaster start. POSIX does document EINVAL for too-few-semaphores-in-set, and is silent on its priority relative to EEXIST, so this behavior arguably conforms to spec. Nonetheless it's quite problematic because EINVAL is also documented to mean that nsems is greater than the system's limit on the number of semaphores per set (SEMMSL). If that is where the problem lies, retrying would just become an infinite loop. To resolve this contradiction, retry after EINVAL, but also install a loop limit that will make us give up regardless of the specific errno after trying 1000 different keys. (1000 is a pretty arbitrary number, but it seems like it should be sufficient.) I like this better than the previous infinite-looping behavior, since it will also keep us out of trouble if (say) we get EACCES due to a system-level permissions problem rather than anything to do with a specific semaphore set. This problem has only been observed in the field in PG 17, which uses a higher nsems value than other branches (cf. 38da053, 810a8b1). That makes it possible to get the failure if a new v17 postmaster has a key collision with an existing postmaster of another branch. In principle though, we might see such a collision against a semaphore set created by some other application, in which case all branches are vulnerable on these platforms. Hence, backpatch. Reported-by: Gavin Panella <gavinpanella@gmail.com> Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/CALL7chmzY3eXHA7zHnODUVGZLSvK3wYCSP0RmcDFHJY8f28Q3g@mail.gmail.com Backpatch-through: 13
1 parent 05f506a commit 21fddb3

File tree

1 file changed

+33
-13
lines changed

1 file changed

+33
-13
lines changed

src/backend/port/sysv_sema.c

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ static int nextSemaNumber; /* next free sem num in last sema set */
6969

7070

7171
static IpcSemaphoreId InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey,
72-
int numSems);
72+
int numSems, bool retry_ok);
7373
static void IpcSemaphoreInitialize(IpcSemaphoreId semId, int semNum,
7474
int value);
7575
static void IpcSemaphoreKill(IpcSemaphoreId semId);
@@ -88,9 +88,13 @@ static void ReleaseSemaphores(int status, Datum arg);
8888
* If we fail with a failure code other than collision-with-existing-set,
8989
* print out an error and abort. Other types of errors suggest nonrecoverable
9090
* problems.
91+
*
92+
* Unfortunately, it's sometimes hard to tell whether errors are
93+
* nonrecoverable. Our caller keeps track of whether continuing to retry
94+
* is sane or not; if not, we abort on failure regardless of the errno.
9195
*/
9296
static IpcSemaphoreId
93-
InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey, int numSems)
97+
InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey, int numSems, bool retry_ok)
9498
{
9599
int semId;
96100

@@ -101,16 +105,27 @@ InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey, int numSems)
101105
int saved_errno = errno;
102106

103107
/*
104-
* Fail quietly if error indicates a collision with existing set. One
105-
* would expect EEXIST, given that we said IPC_EXCL, but perhaps we
106-
* could get a permission violation instead? Also, EIDRM might occur
107-
* if an old set is slated for destruction but not gone yet.
108+
* Fail quietly if error suggests a collision with an existing set and
109+
* our caller has not lost patience.
110+
*
111+
* One would expect EEXIST, given that we said IPC_EXCL, but perhaps
112+
* we could get a permission violation instead. On some platforms
113+
* EINVAL will be reported if the existing set has too few semaphores.
114+
* Also, EIDRM might occur if an old set is slated for destruction but
115+
* not gone yet.
116+
*
117+
* EINVAL is the key reason why we need the caller-level loop limit,
118+
* as it can also mean that the platform's SEMMSL is less than
119+
* numSems, and that condition can't be fixed by trying another key.
108120
*/
109-
if (saved_errno == EEXIST || saved_errno == EACCES
121+
if (retry_ok &&
122+
(saved_errno == EEXIST
123+
|| saved_errno == EACCES
124+
|| saved_errno == EINVAL
110125
#ifdef EIDRM
111-
|| saved_errno == EIDRM
126+
|| saved_errno == EIDRM
112127
#endif
113-
)
128+
))
114129
return -1;
115130

116131
/*
@@ -207,17 +222,22 @@ IpcSemaphoreGetLastPID(IpcSemaphoreId semId, int semNum)
207222
static IpcSemaphoreId
208223
IpcSemaphoreCreate(int numSems)
209224
{
225+
int num_tries = 0;
210226
IpcSemaphoreId semId;
211227
union semun semun;
212228
PGSemaphoreData mysema;
213229

214230
/* Loop till we find a free IPC key */
215-
for (nextSemaKey++;; nextSemaKey++)
231+
for (nextSemaKey++;; nextSemaKey++, num_tries++)
216232
{
217233
pid_t creatorPID;
218234

219-
/* Try to create new semaphore set */
220-
semId = InternalIpcSemaphoreCreate(nextSemaKey, numSems + 1);
235+
/*
236+
* Try to create new semaphore set. Give up after trying 1000
237+
* distinct IPC keys.
238+
*/
239+
semId = InternalIpcSemaphoreCreate(nextSemaKey, numSems + 1,
240+
num_tries < 1000);
221241
if (semId >= 0)
222242
break; /* successful create */
223243

@@ -254,7 +274,7 @@ IpcSemaphoreCreate(int numSems)
254274
/*
255275
* Now try again to create the sema set.
256276
*/
257-
semId = InternalIpcSemaphoreCreate(nextSemaKey, numSems + 1);
277+
semId = InternalIpcSemaphoreCreate(nextSemaKey, numSems + 1, true);
258278
if (semId >= 0)
259279
break; /* successful create */
260280

0 commit comments

Comments
 (0)