Skip to content

Commit e3b3fa8

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 af2dbe8 commit e3b3fa8

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
@@ -73,7 +73,7 @@ static int nextSemaNumber; /* next free sem num in last sema set */
7373

7474

7575
static IpcSemaphoreId InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey,
76-
int numSems);
76+
int numSems, bool retry_ok);
7777
static void IpcSemaphoreInitialize(IpcSemaphoreId semId, int semNum,
7878
int value);
7979
static void IpcSemaphoreKill(IpcSemaphoreId semId);
@@ -92,9 +92,13 @@ static void ReleaseSemaphores(int status, Datum arg);
9292
* If we fail with a failure code other than collision-with-existing-set,
9393
* print out an error and abort. Other types of errors suggest nonrecoverable
9494
* problems.
95+
*
96+
* Unfortunately, it's sometimes hard to tell whether errors are
97+
* nonrecoverable. Our caller keeps track of whether continuing to retry
98+
* is sane or not; if not, we abort on failure regardless of the errno.
9599
*/
96100
static IpcSemaphoreId
97-
InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey, int numSems)
101+
InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey, int numSems, bool retry_ok)
98102
{
99103
int semId;
100104

@@ -105,16 +109,27 @@ InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey, int numSems)
105109
int saved_errno = errno;
106110

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

120135
/*
@@ -211,17 +226,22 @@ IpcSemaphoreGetLastPID(IpcSemaphoreId semId, int semNum)
211226
static IpcSemaphoreId
212227
IpcSemaphoreCreate(int numSems)
213228
{
229+
int num_tries = 0;
214230
IpcSemaphoreId semId;
215231
union semun semun;
216232
PGSemaphoreData mysema;
217233

218234
/* Loop till we find a free IPC key */
219-
for (nextSemaKey++;; nextSemaKey++)
235+
for (nextSemaKey++;; nextSemaKey++, num_tries++)
220236
{
221237
pid_t creatorPID;
222238

223-
/* Try to create new semaphore set */
224-
semId = InternalIpcSemaphoreCreate(nextSemaKey, numSems + 1);
239+
/*
240+
* Try to create new semaphore set. Give up after trying 1000
241+
* distinct IPC keys.
242+
*/
243+
semId = InternalIpcSemaphoreCreate(nextSemaKey, numSems + 1,
244+
num_tries < 1000);
225245
if (semId >= 0)
226246
break; /* successful create */
227247

@@ -258,7 +278,7 @@ IpcSemaphoreCreate(int numSems)
258278
/*
259279
* Now try again to create the sema set.
260280
*/
261-
semId = InternalIpcSemaphoreCreate(nextSemaKey, numSems + 1);
281+
semId = InternalIpcSemaphoreCreate(nextSemaKey, numSems + 1, true);
262282
if (semId >= 0)
263283
break; /* successful create */
264284

0 commit comments

Comments
 (0)