Add code to InternalIpcMemoryCreate() to handle the case where shmget()

returns EINVAL for an existing shared memory segment.  Although it's not
terribly sensible, that behavior does meet the POSIX spec because EINVAL
is the appropriate error code when the existing segment is smaller than the
requested size, and the spec explicitly disclaims any particular ordering of
error checks.  Moreover, it does in fact happen on OS X and probably other
BSD-derived kernels.  (We were able to talk NetBSD into changing their code,
but purging that behavior from the wild completely seems unlikely to happen.)
We need to distinguish collision with a pre-existing segment from invalid size
request in order to behave sensibly, so it's worth some extra code here to get
it right.  Per report from Gavin Kistner and subsequent investigation.

Back-patch to all supported versions, since any of them could get used
with a kernel having the debatable behavior.
This commit is contained in:
Tom Lane 2010-05-01 22:47:15 +00:00
parent 25681e0a19
commit 79c712aae8
1 changed files with 43 additions and 1 deletions

View File

@ -10,7 +10,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/port/sysv_shmem.c,v 1.24.2.3 2007/07/02 20:12:21 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/port/sysv_shmem.c,v 1.24.2.4 2010/05/01 22:47:15 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -85,6 +85,48 @@ InternalIpcMemoryCreate(IpcMemoryKey memKey, uint32 size)
)
return NULL;
/*
* Some BSD-derived kernels are known to return EINVAL, not EEXIST,
* if there is an existing segment but it's smaller than "size"
* (this is a result of poorly-thought-out ordering of error tests).
* To distinguish between collision and invalid size in such cases,
* we make a second try with size = 0. These kernels do not test
* size against SHMMIN in the preexisting-segment case, so we will
* not get EINVAL a second time if there is such a segment.
*/
if (errno == EINVAL)
{
int save_errno = errno;
shmid = shmget(memKey, 0, IPC_CREAT | IPC_EXCL | IPCProtection);
if (shmid < 0)
{
/* As above, fail quietly if we verify a collision */
if (errno == EEXIST || errno == EACCES
#ifdef EIDRM
|| errno == EIDRM
#endif
)
return NULL;
/* Otherwise, fall through to report the original error */
}
else
{
/*
* On most platforms we cannot get here because SHMMIN is
* greater than zero. However, if we do succeed in creating
* a zero-size segment, free it and then fall through to
* report the original error.
*/
if (shmctl(shmid, IPC_RMID, NULL) < 0)
elog(LOG, "shmctl(%d, %d, 0) failed: %m",
(int) shmid, IPC_RMID);
}
errno = save_errno;
}
/*
* Else complain and abort
*/