Commit 82150a05 authored by Noah Misch's avatar Noah Misch

Revert "Consistently test for in-use shared memory."

This reverts commits 2f932f71,
16ee6eaf and
6f0e1900.  The buildfarm has revealed
several bugs.  Back-patch like the original commits.

Discussion: https://postgr.es/m/20190404145319.GA1720877@rfd.leadboat.com
parent 794c543b
...@@ -430,13 +430,13 @@ ifeq ($(enable_tap_tests),yes) ...@@ -430,13 +430,13 @@ ifeq ($(enable_tap_tests),yes)
define prove_installcheck define prove_installcheck
rm -rf '$(CURDIR)'/tmp_check rm -rf '$(CURDIR)'/tmp_check
$(MKDIR_P) '$(CURDIR)'/tmp_check $(MKDIR_P) '$(CURDIR)'/tmp_check
cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl) cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
endef endef
define prove_check define prove_check
rm -rf '$(CURDIR)'/tmp_check rm -rf '$(CURDIR)'/tmp_check
$(MKDIR_P) '$(CURDIR)'/tmp_check $(MKDIR_P) '$(CURDIR)'/tmp_check
cd $(srcdir) && TESTDIR='$(CURDIR)' $(with_temp_install) PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl) cd $(srcdir) && TESTDIR='$(CURDIR)' $(with_temp_install) PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
endef endef
else else
......
...@@ -72,26 +72,6 @@ ...@@ -72,26 +72,6 @@
typedef key_t IpcMemoryKey; /* shared memory key passed to shmget(2) */ typedef key_t IpcMemoryKey; /* shared memory key passed to shmget(2) */
typedef int IpcMemoryId; /* shared memory ID returned by shmget(2) */ typedef int IpcMemoryId; /* shared memory ID returned by shmget(2) */
/*
* How does a given IpcMemoryId relate to this PostgreSQL process?
*
* One could recycle unattached segments of different data directories if we
* distinguished that case from other SHMSTATE_FOREIGN cases. Doing so would
* cause us to visit less of the key space, making us less likely to detect a
* SHMSTATE_ATTACHED key. It would also complicate the concurrency analysis,
* in that postmasters of different data directories could simultaneously
* attempt to recycle a given key. We'll waste keys longer in some cases, but
* avoiding the problems of the alternative justifies that loss.
*/
typedef enum
{
SHMSTATE_ANALYSIS_FAILURE, /* unexpected failure to analyze the ID */
SHMSTATE_ATTACHED, /* pertinent to DataDir, has attached PIDs */
SHMSTATE_ENOENT, /* no segment of that ID */
SHMSTATE_FOREIGN, /* exists, but not pertinent to DataDir */
SHMSTATE_UNATTACHED /* pertinent to DataDir, no attached PIDs */
} IpcMemoryState;
unsigned long UsedShmemSegID = 0; unsigned long UsedShmemSegID = 0;
void *UsedShmemSegAddr = NULL; void *UsedShmemSegAddr = NULL;
...@@ -102,8 +82,8 @@ static void *AnonymousShmem = NULL; ...@@ -102,8 +82,8 @@ static void *AnonymousShmem = NULL;
static void *InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size); static void *InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size);
static void IpcMemoryDetach(int status, Datum shmaddr); static void IpcMemoryDetach(int status, Datum shmaddr);
static void IpcMemoryDelete(int status, Datum shmId); static void IpcMemoryDelete(int status, Datum shmId);
static IpcMemoryState PGSharedMemoryAttach(IpcMemoryId shmId, static PGShmemHeader *PGSharedMemoryAttach(IpcMemoryKey key,
PGShmemHeader **addr); IpcMemoryId *shmid);
/* /*
...@@ -307,36 +287,11 @@ IpcMemoryDelete(int status, Datum shmId) ...@@ -307,36 +287,11 @@ IpcMemoryDelete(int status, Datum shmId)
bool bool
PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2)
{ {
PGShmemHeader *memAddress; IpcMemoryId shmId = (IpcMemoryId) id2;
IpcMemoryState state;
state = PGSharedMemoryAttach((IpcMemoryId) id2, &memAddress);
if (memAddress && shmdt(memAddress) < 0)
elog(LOG, "shmdt(%p) failed: %m", memAddress);
switch (state)
{
case SHMSTATE_ENOENT:
case SHMSTATE_FOREIGN:
case SHMSTATE_UNATTACHED:
return false;
case SHMSTATE_ANALYSIS_FAILURE:
case SHMSTATE_ATTACHED:
return true;
}
return true;
}
/* See comment at IpcMemoryState. */
static IpcMemoryState
PGSharedMemoryAttach(IpcMemoryId shmId,
PGShmemHeader **addr)
{
struct shmid_ds shmStat; struct shmid_ds shmStat;
struct stat statbuf; struct stat statbuf;
PGShmemHeader *hdr; PGShmemHeader *hdr;
*addr = NULL;
/* /*
* We detect whether a shared memory segment is in use by seeing whether * We detect whether a shared memory segment is in use by seeing whether
* it (a) exists and (b) has any processes attached to it. * it (a) exists and (b) has any processes attached to it.
...@@ -349,7 +304,7 @@ PGSharedMemoryAttach(IpcMemoryId shmId, ...@@ -349,7 +304,7 @@ PGSharedMemoryAttach(IpcMemoryId shmId,
* exists. * exists.
*/ */
if (errno == EINVAL) if (errno == EINVAL)
return SHMSTATE_ENOENT; return false;
/* /*
* EACCES implies that the segment belongs to some other userid, which * EACCES implies that the segment belongs to some other userid, which
...@@ -357,7 +312,7 @@ PGSharedMemoryAttach(IpcMemoryId shmId, ...@@ -357,7 +312,7 @@ PGSharedMemoryAttach(IpcMemoryId shmId,
* is relevant to our data directory). * is relevant to our data directory).
*/ */
if (errno == EACCES) if (errno == EACCES)
return SHMSTATE_FOREIGN; return false;
/* /*
* Some Linux kernel versions (in fact, all of them as of July 2007) * Some Linux kernel versions (in fact, all of them as of July 2007)
...@@ -368,7 +323,7 @@ PGSharedMemoryAttach(IpcMemoryId shmId, ...@@ -368,7 +323,7 @@ PGSharedMemoryAttach(IpcMemoryId shmId,
*/ */
#ifdef HAVE_LINUX_EIDRM_BUG #ifdef HAVE_LINUX_EIDRM_BUG
if (errno == EIDRM) if (errno == EIDRM)
return SHMSTATE_ENOENT; return false;
#endif #endif
/* /*
...@@ -376,26 +331,25 @@ PGSharedMemoryAttach(IpcMemoryId shmId, ...@@ -376,26 +331,25 @@ PGSharedMemoryAttach(IpcMemoryId shmId,
* only likely case is EIDRM, which implies that the segment has been * only likely case is EIDRM, which implies that the segment has been
* IPC_RMID'd but there are still processes attached to it. * IPC_RMID'd but there are still processes attached to it.
*/ */
return SHMSTATE_ANALYSIS_FAILURE; return true;
} }
/* If it has no attached processes, it's not in use */
if (shmStat.shm_nattch == 0)
return false;
/* /*
* Try to attach to the segment and see if it matches our data directory. * Try to attach to the segment and see if it matches our data directory.
* This avoids shmid-conflict problems on machines that are running * This avoids shmid-conflict problems on machines that are running
* several postmasters under the same userid. * several postmasters under the same userid.
*/ */
if (stat(DataDir, &statbuf) < 0) if (stat(DataDir, &statbuf) < 0)
return SHMSTATE_ANALYSIS_FAILURE; /* can't stat; be conservative */ return true; /* if can't stat, be conservative */
hdr = (PGShmemHeader *) shmat(shmId, NULL, PG_SHMAT_FLAGS);
/*
* If we can't attach, be conservative. This may fail if postmaster.pid
* furnished the shmId and another user created a world-readable segment
* of the same shmId.
*/
hdr = (PGShmemHeader *) shmat(shmId, UsedShmemSegAddr, PG_SHMAT_FLAGS);
if (hdr == (PGShmemHeader *) -1) if (hdr == (PGShmemHeader *) -1)
return SHMSTATE_ANALYSIS_FAILURE; return true; /* if can't attach, be conservative */
*addr = hdr;
if (hdr->magic != PGShmemMagic || if (hdr->magic != PGShmemMagic ||
hdr->device != statbuf.st_dev || hdr->device != statbuf.st_dev ||
...@@ -403,12 +357,16 @@ PGSharedMemoryAttach(IpcMemoryId shmId, ...@@ -403,12 +357,16 @@ PGSharedMemoryAttach(IpcMemoryId shmId,
{ {
/* /*
* It's either not a Postgres segment, or not one for my data * It's either not a Postgres segment, or not one for my data
* directory. * directory. In either case it poses no threat.
*/ */
return SHMSTATE_FOREIGN; shmdt((void *) hdr);
return false;
} }
return shmStat.shm_nattch == 0 ? SHMSTATE_UNATTACHED : SHMSTATE_ATTACHED; /* Trouble --- looks a lot like there's still live backends */
shmdt((void *) hdr);
return true;
} }
#ifdef MAP_HUGETLB #ifdef MAP_HUGETLB
...@@ -580,21 +538,25 @@ AnonymousShmemDetach(int status, Datum arg) ...@@ -580,21 +538,25 @@ AnonymousShmemDetach(int status, Datum arg)
* standard header. Also, register an on_shmem_exit callback to release * standard header. Also, register an on_shmem_exit callback to release
* the storage. * the storage.
* *
* Dead Postgres segments pertinent to this DataDir are recycled if found, but * Dead Postgres segments are recycled if found, but we do not fail upon
* we do not fail upon collision with foreign shmem segments. The idea here * collision with non-Postgres shmem segments. The idea here is to detect and
* is to detect and re-use keys that may have been assigned by a crashed * re-use keys that may have been assigned by a crashed postmaster or backend.
* postmaster or backend. *
* makePrivate means to always create a new segment, rather than attach to
* or recycle any existing segment.
* *
* The port number is passed for possible use as a key (for SysV, we use * The port number is passed for possible use as a key (for SysV, we use
* it to generate the starting shmem key). * it to generate the starting shmem key). In a standalone backend,
* zero will be passed.
*/ */
PGShmemHeader * PGShmemHeader *
PGSharedMemoryCreate(Size size, int port, PGSharedMemoryCreate(Size size, bool makePrivate, int port,
PGShmemHeader **shim) PGShmemHeader **shim)
{ {
IpcMemoryKey NextShmemSegID; IpcMemoryKey NextShmemSegID;
void *memAddress; void *memAddress;
PGShmemHeader *hdr; PGShmemHeader *hdr;
IpcMemoryId shmid;
struct stat statbuf; struct stat statbuf;
Size sysvsize; Size sysvsize;
...@@ -626,20 +588,11 @@ PGSharedMemoryCreate(Size size, int port, ...@@ -626,20 +588,11 @@ PGSharedMemoryCreate(Size size, int port,
/* Make sure PGSharedMemoryAttach doesn't fail without need */ /* Make sure PGSharedMemoryAttach doesn't fail without need */
UsedShmemSegAddr = NULL; UsedShmemSegAddr = NULL;
/* /* Loop till we find a free IPC key */
* Loop till we find a free IPC key. Trust CreateDataDirLockFile() to NextShmemSegID = port * 1000;
* ensure no more than one postmaster per data directory can enter this
* loop simultaneously. (CreateDataDirLockFile() does not ensure that,
* but prefer fixing it over coping here.)
*/
NextShmemSegID = 1 + port * 1000;
for (;;) for (NextShmemSegID++;; NextShmemSegID++)
{ {
IpcMemoryId shmid;
PGShmemHeader *oldhdr;
IpcMemoryState state;
/* Try to create new segment */ /* Try to create new segment */
memAddress = InternalIpcMemoryCreate(NextShmemSegID, sysvsize); memAddress = InternalIpcMemoryCreate(NextShmemSegID, sysvsize);
if (memAddress) if (memAddress)
...@@ -647,71 +600,58 @@ PGSharedMemoryCreate(Size size, int port, ...@@ -647,71 +600,58 @@ PGSharedMemoryCreate(Size size, int port,
/* Check shared memory and possibly remove and recreate */ /* Check shared memory and possibly remove and recreate */
if (makePrivate) /* a standalone backend shouldn't do this */
continue;
if ((memAddress = PGSharedMemoryAttach(NextShmemSegID, &shmid)) == NULL)
continue; /* can't attach, not one of mine */
/* /*
* shmget() failure is typically EACCES, hence SHMSTATE_FOREIGN. * If I am not the creator and it belongs to an extant process,
* ENOENT, a narrow possibility, implies SHMSTATE_ENOENT, but one can * continue.
* safely treat SHMSTATE_ENOENT like SHMSTATE_FOREIGN.
*/ */
shmid = shmget(NextShmemSegID, sizeof(PGShmemHeader), 0); hdr = (PGShmemHeader *) memAddress;
if (shmid < 0) if (hdr->creatorPID != getpid())
{ {
oldhdr = NULL; if (kill(hdr->creatorPID, 0) == 0 || errno != ESRCH)
state = SHMSTATE_FOREIGN; {
shmdt(memAddress);
continue; /* segment belongs to a live process */
}
} }
else
state = PGSharedMemoryAttach(shmid, &oldhdr);
switch (state)
{
case SHMSTATE_ANALYSIS_FAILURE:
case SHMSTATE_ATTACHED:
ereport(FATAL,
(errcode(ERRCODE_LOCK_FILE_EXISTS),
errmsg("pre-existing shared memory block (key %lu, ID %lu) is still in use",
(unsigned long) NextShmemSegID,
(unsigned long) shmid),
errhint("Terminate any old server processes associated with data directory \"%s\".",
DataDir)));
break;
case SHMSTATE_ENOENT:
/* /*
* To our surprise, some other process deleted since our last * The segment appears to be from a dead Postgres process, or from a
* InternalIpcMemoryCreate(). Moments earlier, we would have * previous cycle of life in this same process. Zap it, if possible,
* seen SHMSTATE_FOREIGN. Try that same ID again. * and any associated dynamic shared memory segments, as well. This
*/ * probably shouldn't fail, but if it does, assume the segment belongs
elog(LOG, * to someone else after all, and continue quietly.
"shared memory block (key %lu, ID %lu) deleted during startup", */
(unsigned long) NextShmemSegID, if (hdr->dsm_control != 0)
(unsigned long) shmid); dsm_cleanup_using_control_segment(hdr->dsm_control);
break; shmdt(memAddress);
case SHMSTATE_FOREIGN: if (shmctl(shmid, IPC_RMID, NULL) < 0)
NextShmemSegID++; continue;
break;
case SHMSTATE_UNATTACHED:
/* /*
* The segment pertains to DataDir, and every process that had * Now try again to create the segment.
* used it has died or detached. Zap it, if possible, and any */
* associated dynamic shared memory segments, as well. This memAddress = InternalIpcMemoryCreate(NextShmemSegID, sysvsize);
* shouldn't fail, but if it does, assume the segment belongs if (memAddress)
* to someone else after all, and try the next candidate. break; /* successful create and attach */
* Otherwise, try again to create the segment. That may fail
* if some other process creates the same shmem key before we
* do, in which case we'll try the next key.
*/
if (oldhdr->dsm_control != 0)
dsm_cleanup_using_control_segment(oldhdr->dsm_control);
if (shmctl(shmid, IPC_RMID, NULL) < 0)
NextShmemSegID++;
break;
}
if (oldhdr && shmdt(oldhdr) < 0) /*
elog(LOG, "shmdt(%p) failed: %m", oldhdr); * Can only get here if some other process managed to create the same
* shmem key before we did. Let him have that one, loop around to try
* next key.
*/
} }
/* Initialize new segment. */ /*
* OK, we created a new segment. Mark it as created by this process. The
* order of assignments here is critical so that another Postgres process
* can't see the header as valid but belonging to an invalid PID!
*/
hdr = (PGShmemHeader *) memAddress; hdr = (PGShmemHeader *) memAddress;
hdr->creatorPID = getpid(); hdr->creatorPID = getpid();
hdr->magic = PGShmemMagic; hdr->magic = PGShmemMagic;
...@@ -767,8 +707,7 @@ void ...@@ -767,8 +707,7 @@ void
PGSharedMemoryReAttach(void) PGSharedMemoryReAttach(void)
{ {
IpcMemoryId shmid; IpcMemoryId shmid;
PGShmemHeader *hdr; void *hdr;
IpcMemoryState state;
void *origUsedShmemSegAddr = UsedShmemSegAddr; void *origUsedShmemSegAddr = UsedShmemSegAddr;
Assert(UsedShmemSegAddr != NULL); Assert(UsedShmemSegAddr != NULL);
...@@ -781,18 +720,14 @@ PGSharedMemoryReAttach(void) ...@@ -781,18 +720,14 @@ PGSharedMemoryReAttach(void)
#endif #endif
elog(DEBUG3, "attaching to %p", UsedShmemSegAddr); elog(DEBUG3, "attaching to %p", UsedShmemSegAddr);
shmid = shmget(UsedShmemSegID, sizeof(PGShmemHeader), 0); hdr = (void *) PGSharedMemoryAttach((IpcMemoryKey) UsedShmemSegID, &shmid);
if (shmid < 0) if (hdr == NULL)
state = SHMSTATE_FOREIGN;
else
state = PGSharedMemoryAttach(shmid, &hdr);
if (state != SHMSTATE_ATTACHED)
elog(FATAL, "could not reattach to shared memory (key=%d, addr=%p): %m", elog(FATAL, "could not reattach to shared memory (key=%d, addr=%p): %m",
(int) UsedShmemSegID, UsedShmemSegAddr); (int) UsedShmemSegID, UsedShmemSegAddr);
if (hdr != origUsedShmemSegAddr) if (hdr != origUsedShmemSegAddr)
elog(FATAL, "reattaching to shared memory returned unexpected address (got %p, expected %p)", elog(FATAL, "reattaching to shared memory returned unexpected address (got %p, expected %p)",
hdr, origUsedShmemSegAddr); hdr, origUsedShmemSegAddr);
dsm_set_control_handle(hdr->dsm_control); dsm_set_control_handle(((PGShmemHeader *) hdr)->dsm_control);
UsedShmemSegAddr = hdr; /* probably redundant */ UsedShmemSegAddr = hdr; /* probably redundant */
} }
...@@ -866,3 +801,31 @@ PGSharedMemoryDetach(void) ...@@ -866,3 +801,31 @@ PGSharedMemoryDetach(void)
AnonymousShmem = NULL; AnonymousShmem = NULL;
} }
} }
/*
* Attach to shared memory and make sure it has a Postgres header
*
* Returns attach address if OK, else NULL
*/
static PGShmemHeader *
PGSharedMemoryAttach(IpcMemoryKey key, IpcMemoryId *shmid)
{
PGShmemHeader *hdr;
if ((*shmid = shmget(key, sizeof(PGShmemHeader), 0)) < 0)
return NULL;
hdr = (PGShmemHeader *) shmat(*shmid, UsedShmemSegAddr, PG_SHMAT_FLAGS);
if (hdr == (PGShmemHeader *) -1)
return NULL; /* failed: must be some other app's */
if (hdr->magic != PGShmemMagic)
{
shmdt((void *) hdr);
return NULL; /* segment belongs to a non-Postgres app */
}
return hdr;
}
...@@ -170,9 +170,14 @@ EnableLockPagesPrivilege(int elevel) ...@@ -170,9 +170,14 @@ EnableLockPagesPrivilege(int elevel)
* *
* Create a shared memory segment of the given size and initialize its * Create a shared memory segment of the given size and initialize its
* standard header. * standard header.
*
* makePrivate means to always create a new segment, rather than attach to
* or recycle any existing segment. On win32, we always create a new segment,
* since there is no need for recycling (segments go away automatically
* when the last backend exits)
*/ */
PGShmemHeader * PGShmemHeader *
PGSharedMemoryCreate(Size size, int port, PGSharedMemoryCreate(Size size, bool makePrivate, int port,
PGShmemHeader **shim) PGShmemHeader **shim)
{ {
void *memAddress; void *memAddress;
......
...@@ -2604,7 +2604,7 @@ reset_shared(int port) ...@@ -2604,7 +2604,7 @@ reset_shared(int port)
* determine IPC keys. This helps ensure that we will clean up dead IPC * determine IPC keys. This helps ensure that we will clean up dead IPC
* objects if the postmaster crashes and is restarted. * objects if the postmaster crashes and is restarted.
*/ */
CreateSharedMemoryAndSemaphores(port); CreateSharedMemoryAndSemaphores(false, port);
} }
...@@ -4945,7 +4945,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -4945,7 +4945,7 @@ SubPostmasterMain(int argc, char *argv[])
InitProcess(); InitProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(0); CreateSharedMemoryAndSemaphores(false, 0);
/* And run the backend */ /* And run the backend */
BackendRun(&port); /* does not return */ BackendRun(&port); /* does not return */
...@@ -4959,7 +4959,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -4959,7 +4959,7 @@ SubPostmasterMain(int argc, char *argv[])
InitAuxiliaryProcess(); InitAuxiliaryProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(0); CreateSharedMemoryAndSemaphores(false, 0);
AuxiliaryProcessMain(argc - 2, argv + 2); /* does not return */ AuxiliaryProcessMain(argc - 2, argv + 2); /* does not return */
} }
...@@ -4972,7 +4972,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -4972,7 +4972,7 @@ SubPostmasterMain(int argc, char *argv[])
InitProcess(); InitProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(0); CreateSharedMemoryAndSemaphores(false, 0);
AutoVacLauncherMain(argc - 2, argv + 2); /* does not return */ AutoVacLauncherMain(argc - 2, argv + 2); /* does not return */
} }
...@@ -4985,7 +4985,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -4985,7 +4985,7 @@ SubPostmasterMain(int argc, char *argv[])
InitProcess(); InitProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(0); CreateSharedMemoryAndSemaphores(false, 0);
AutoVacWorkerMain(argc - 2, argv + 2); /* does not return */ AutoVacWorkerMain(argc - 2, argv + 2); /* does not return */
} }
...@@ -5003,7 +5003,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -5003,7 +5003,7 @@ SubPostmasterMain(int argc, char *argv[])
InitProcess(); InitProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(0); CreateSharedMemoryAndSemaphores(false, 0);
/* Fetch MyBgworkerEntry from shared memory */ /* Fetch MyBgworkerEntry from shared memory */
shmem_slot = atoi(argv[1] + 15); shmem_slot = atoi(argv[1] + 15);
......
...@@ -89,9 +89,12 @@ RequestAddinShmemSpace(Size size) ...@@ -89,9 +89,12 @@ RequestAddinShmemSpace(Size size)
* through the same code as before. (Note that the called routines mostly * through the same code as before. (Note that the called routines mostly
* check IsUnderPostmaster, rather than EXEC_BACKEND, to detect this case. * check IsUnderPostmaster, rather than EXEC_BACKEND, to detect this case.
* This is a bit code-wasteful and could be cleaned up.) * This is a bit code-wasteful and could be cleaned up.)
*
* If "makePrivate" is true then we only need private memory, not shared
* memory. This is true for a standalone backend, false for a postmaster.
*/ */
void void
CreateSharedMemoryAndSemaphores(int port) CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
{ {
PGShmemHeader *shim = NULL; PGShmemHeader *shim = NULL;
...@@ -163,7 +166,7 @@ CreateSharedMemoryAndSemaphores(int port) ...@@ -163,7 +166,7 @@ CreateSharedMemoryAndSemaphores(int port)
/* /*
* Create the shmem segment * Create the shmem segment
*/ */
seghdr = PGSharedMemoryCreate(size, port, &shim); seghdr = PGSharedMemoryCreate(size, makePrivate, port, &shim);
InitShmemAccess(seghdr); InitShmemAccess(seghdr);
...@@ -184,9 +187,12 @@ CreateSharedMemoryAndSemaphores(int port) ...@@ -184,9 +187,12 @@ CreateSharedMemoryAndSemaphores(int port)
{ {
/* /*
* We are reattaching to an existing shared memory segment. This * We are reattaching to an existing shared memory segment. This
* should only be reached in the EXEC_BACKEND case. * should only be reached in the EXEC_BACKEND case, and even then only
* with makePrivate == false.
*/ */
#ifndef EXEC_BACKEND #ifdef EXEC_BACKEND
Assert(!makePrivate);
#else
elog(PANIC, "should be attached to shared memory already"); elog(PANIC, "should be attached to shared memory already");
#endif #endif
} }
......
...@@ -446,11 +446,9 @@ InitCommunication(void) ...@@ -446,11 +446,9 @@ InitCommunication(void)
{ {
/* /*
* We're running a postgres bootstrap process or a standalone backend. * We're running a postgres bootstrap process or a standalone backend.
* Though we won't listen on PostPortNumber, use it to select a shmem * Create private "shmem" and semaphores.
* key. This increases the chance of detecting a leftover live
* backend of this DataDir.
*/ */
CreateSharedMemoryAndSemaphores(PostPortNumber); CreateSharedMemoryAndSemaphores(true, 0);
} }
} }
......
...@@ -76,6 +76,6 @@ extern void on_exit_reset(void); ...@@ -76,6 +76,6 @@ extern void on_exit_reset(void);
/* ipci.c */ /* ipci.c */
extern PGDLLIMPORT shmem_startup_hook_type shmem_startup_hook; extern PGDLLIMPORT shmem_startup_hook_type shmem_startup_hook;
extern void CreateSharedMemoryAndSemaphores(int port); extern void CreateSharedMemoryAndSemaphores(bool makePrivate, int port);
#endif /* IPC_H */ #endif /* IPC_H */
...@@ -30,7 +30,7 @@ typedef struct PGShmemHeader /* standard header for all Postgres shmem */ ...@@ -30,7 +30,7 @@ typedef struct PGShmemHeader /* standard header for all Postgres shmem */
{ {
int32 magic; /* magic # to identify Postgres segments */ int32 magic; /* magic # to identify Postgres segments */
#define PGShmemMagic 679834894 #define PGShmemMagic 679834894
pid_t creatorPID; /* PID of creating process (set but unread) */ pid_t creatorPID; /* PID of creating process */
Size totalsize; /* total size of segment */ Size totalsize; /* total size of segment */
Size freeoffset; /* offset to first free space */ Size freeoffset; /* offset to first free space */
dsm_handle dsm_control; /* ID of dynamic shared memory control seg */ dsm_handle dsm_control; /* ID of dynamic shared memory control seg */
...@@ -81,8 +81,8 @@ extern void PGSharedMemoryReAttach(void); ...@@ -81,8 +81,8 @@ extern void PGSharedMemoryReAttach(void);
extern void PGSharedMemoryNoReAttach(void); extern void PGSharedMemoryNoReAttach(void);
#endif #endif
extern PGShmemHeader *PGSharedMemoryCreate(Size size, int port, extern PGShmemHeader *PGSharedMemoryCreate(Size size, bool makePrivate,
PGShmemHeader **shim); int port, PGShmemHeader **shim);
extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2); extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2);
extern void PGSharedMemoryDetach(void); extern void PGSharedMemoryDetach(void);
......
...@@ -104,8 +104,7 @@ our @EXPORT = qw( ...@@ -104,8 +104,7 @@ our @EXPORT = qw(
get_new_node get_new_node
); );
our ($use_tcp, $test_localhost, $test_pghost, $last_host_assigned, our ($test_localhost, $test_pghost, $last_port_assigned, @all_nodes, $died);
$last_port_assigned, @all_nodes, $died);
# Windows path to virtual file system root # Windows path to virtual file system root
...@@ -119,14 +118,13 @@ if ($Config{osname} eq 'msys') ...@@ -119,14 +118,13 @@ if ($Config{osname} eq 'msys')
INIT INIT
{ {
# Set PGHOST for backward compatibility. This doesn't work for own_host # PGHOST is set once and for all through a single series of tests when
# nodes, so prefer to not rely on this when writing new tests. # this module is loaded.
$use_tcp = $TestLib::windows_os; $test_localhost = "127.0.0.1";
$test_localhost = "127.0.0.1"; $test_pghost =
$last_host_assigned = 1; $TestLib::windows_os ? $test_localhost : TestLib::tempdir_short;
$test_pghost = $use_tcp ? $test_localhost : TestLib::tempdir_short; $ENV{PGHOST} = $test_pghost;
$ENV{PGHOST} = $test_pghost; $ENV{PGDATABASE} = 'postgres';
$ENV{PGDATABASE} = 'postgres';
# Tracking of last port value assigned to accelerate free port lookup. # Tracking of last port value assigned to accelerate free port lookup.
$last_port_assigned = int(rand() * 16384) + 49152; $last_port_assigned = int(rand() * 16384) + 49152;
...@@ -157,9 +155,7 @@ sub new ...@@ -157,9 +155,7 @@ sub new
_host => $pghost, _host => $pghost,
_basedir => "$TestLib::tmp_check/t_${testname}_${name}_data", _basedir => "$TestLib::tmp_check/t_${testname}_${name}_data",
_name => $name, _name => $name,
_logfile_generation => 0, _logfile => "$TestLib::log_path/${testname}_${name}.log"
_logfile_base => "$TestLib::log_path/${testname}_${name}",
_logfile => "$TestLib::log_path/${testname}_${name}.log"
}; };
bless $self, $class; bless $self, $class;
...@@ -477,9 +473,8 @@ sub init ...@@ -477,9 +473,8 @@ sub init
print $conf "max_wal_senders = 0\n"; print $conf "max_wal_senders = 0\n";
} }
if ($use_tcp) if ($TestLib::windows_os)
{ {
print $conf "unix_socket_directories = ''\n";
print $conf "listen_addresses = '$host'\n"; print $conf "listen_addresses = '$host'\n";
} }
else else
...@@ -541,11 +536,12 @@ sub backup ...@@ -541,11 +536,12 @@ sub backup
{ {
my ($self, $backup_name) = @_; my ($self, $backup_name) = @_;
my $backup_path = $self->backup_dir . '/' . $backup_name; my $backup_path = $self->backup_dir . '/' . $backup_name;
my $port = $self->port;
my $name = $self->name; my $name = $self->name;
print "# Taking pg_basebackup $backup_name from node \"$name\"\n"; print "# Taking pg_basebackup $backup_name from node \"$name\"\n";
TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-h', TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-p', $port,
$self->host, '-p', $self->port, '--no-sync'); '--no-sync');
print "# Backup finished\n"; print "# Backup finished\n";
return; return;
} }
...@@ -655,7 +651,6 @@ sub init_from_backup ...@@ -655,7 +651,6 @@ sub init_from_backup
{ {
my ($self, $root_node, $backup_name, %params) = @_; my ($self, $root_node, $backup_name, %params) = @_;
my $backup_path = $root_node->backup_dir . '/' . $backup_name; my $backup_path = $root_node->backup_dir . '/' . $backup_name;
my $host = $self->host;
my $port = $self->port; my $port = $self->port;
my $node_name = $self->name; my $node_name = $self->name;
my $root_name = $root_node->name; my $root_name = $root_node->name;
...@@ -682,15 +677,6 @@ sub init_from_backup ...@@ -682,15 +677,6 @@ sub init_from_backup
qq( qq(
port = $port port = $port
)); ));
if ($use_tcp)
{
$self->append_conf('postgresql.conf', "listen_addresses = '$host'");
}
else
{
$self->append_conf('postgresql.conf',
"unix_socket_directories = '$host'");
}
$self->enable_streaming($root_node) if $params{has_streaming}; $self->enable_streaming($root_node) if $params{has_streaming};
$self->enable_restoring($root_node) if $params{has_restoring}; $self->enable_restoring($root_node) if $params{has_restoring};
return; return;
...@@ -698,45 +684,17 @@ port = $port ...@@ -698,45 +684,17 @@ port = $port
=pod =pod
=item $node->rotate_logfile() =item $node->start()
Switch to a new PostgreSQL log file. This does not alter any running
PostgreSQL process. Subsequent method calls, including pg_ctl invocations,
will use the new name. Return the new name.
=cut
sub rotate_logfile
{
my ($self) = @_;
$self->{_logfile} = sprintf('%s_%d.log',
$self->{_logfile_base},
++$self->{_logfile_generation});
return $self->{_logfile};
}
=pod
=item $node->start(%params) => success_or_failure
Wrapper for pg_ctl start Wrapper for pg_ctl start
Start the node and wait until it is ready to accept connections. Start the node and wait until it is ready to accept connections.
=over
=item fail_ok => 1
By default, failure terminates the entire F<prove> invocation. If given,
instead return a true or false value to indicate success or failure.
=back
=cut =cut
sub start sub start
{ {
my ($self, %params) = @_; my ($self) = @_;
my $port = $self->port; my $port = $self->port;
my $pgdata = $self->data_dir; my $pgdata = $self->data_dir;
my $name = $self->name; my $name = $self->name;
...@@ -763,34 +721,10 @@ sub start ...@@ -763,34 +721,10 @@ sub start
{ {
print "# pg_ctl start failed; logfile:\n"; print "# pg_ctl start failed; logfile:\n";
print TestLib::slurp_file($self->logfile); print TestLib::slurp_file($self->logfile);
BAIL_OUT("pg_ctl start failed") unless $params{fail_ok}; BAIL_OUT("pg_ctl start failed");
return 0;
} }
$self->_update_pid(1); $self->_update_pid(1);
return 1;
}
=pod
=item $node->kill9()
Send SIGKILL (signal 9) to the postmaster.
Note: if the node is already known stopped, this does nothing.
However, if we think it's running and it's not, it's important for
this to fail. Otherwise, tests might fail to detect server crashes.
=cut
sub kill9
{
my ($self) = @_;
my $name = $self->name;
return unless defined $self->{_pid};
print "### Killing node \"$name\" using signal 9\n";
kill(9, $self->{_pid}) or BAIL_OUT("kill(9, $self->{_pid}) failed");
$self->{_pid} = undef;
return; return;
} }
...@@ -1031,7 +965,7 @@ sub _update_pid ...@@ -1031,7 +965,7 @@ sub _update_pid
=pod =pod
=item PostgresNode->get_new_node(node_name, %params) =item PostgresNode->get_new_node(node_name)
Build a new object of class C<PostgresNode> (or of a subclass, if you have Build a new object of class C<PostgresNode> (or of a subclass, if you have
one), assigning a free port number. Remembers the node, to prevent its port one), assigning a free port number. Remembers the node, to prevent its port
...@@ -1040,22 +974,6 @@ shut down when the test script exits. ...@@ -1040,22 +974,6 @@ shut down when the test script exits.
You should generally use this instead of C<PostgresNode::new(...)>. You should generally use this instead of C<PostgresNode::new(...)>.
=over
=item port => [1,65535]
By default, this function assigns a port number to each node. Specify this to
force a particular port number. The caller is responsible for evaluating
potential conflicts and privilege requirements.
=item own_host => 1
By default, all nodes use the same PGHOST value. If specified, generate a
PGHOST specific to this node. This allows multiple nodes to use the same
port.
=back
For backwards compatibility, it is also exported as a standalone function, For backwards compatibility, it is also exported as a standalone function,
which can only create objects of class C<PostgresNode>. which can only create objects of class C<PostgresNode>.
...@@ -1064,11 +982,10 @@ which can only create objects of class C<PostgresNode>. ...@@ -1064,11 +982,10 @@ which can only create objects of class C<PostgresNode>.
sub get_new_node sub get_new_node
{ {
my $class = 'PostgresNode'; my $class = 'PostgresNode';
$class = shift if scalar(@_) % 2 != 1; $class = shift if 1 < scalar @_;
my ($name, %params) = @_; my $name = shift;
my $port_is_forced = defined $params{port}; my $found = 0;
my $found = $port_is_forced; my $port = $last_port_assigned;
my $port = $port_is_forced ? $params{port} : $last_port_assigned;
while ($found == 0) while ($found == 0)
{ {
...@@ -1085,15 +1002,13 @@ sub get_new_node ...@@ -1085,15 +1002,13 @@ sub get_new_node
$found = 0 if ($node->port == $port); $found = 0 if ($node->port == $port);
} }
# Check to see if anything else is listening on this TCP port. Accept # Check to see if anything else is listening on this TCP port.
# only ports available for all possible listen_addresses values, so # This is *necessary* on Windows, and seems like a good idea
# the caller can harness this port for the widest range of purposes. # on Unixen as well, even though we don't ask the postmaster
# This is *necessary* on Windows, and seems like a good idea on Unixen # to open a TCP port on Unix.
# as well, even though we don't ask the postmaster to open a TCP port
# on Unix.
if ($found == 1) if ($found == 1)
{ {
my $iaddr = inet_aton('0.0.0.0'); my $iaddr = inet_aton($test_localhost);
my $paddr = sockaddr_in($port, $iaddr); my $paddr = sockaddr_in($port, $iaddr);
my $proto = getprotobyname("tcp"); my $proto = getprotobyname("tcp");
...@@ -1109,35 +1024,16 @@ sub get_new_node ...@@ -1109,35 +1024,16 @@ sub get_new_node
} }
} }
print "# Found port $port\n"; print "# Found free port $port\n";
# Select a host.
my $host = $test_pghost;
if ($params{own_host})
{
if ($use_tcp)
{
# This assumes $use_tcp platforms treat every address in
# 127.0.0.1/24, not just 127.0.0.1, as a usable loopback.
$last_host_assigned++;
$last_host_assigned > 254 and BAIL_OUT("too many own_host nodes");
$host = '127.0.0.' . $last_host_assigned;
}
else
{
$host = "$test_pghost/$name"; # Assume $name =~ /^[-_a-zA-Z0-9]+$/
mkdir $host;
}
}
# Lock port number found by creating a new node # Lock port number found by creating a new node
my $node = $class->new($name, $host, $port); my $node = $class->new($name, $test_pghost, $port);
# Add node to list of nodes # Add node to list of nodes
push(@all_nodes, $node); push(@all_nodes, $node);
# And update port for next time # And update port for next time
$port_is_forced or $last_port_assigned = $port; $last_port_assigned = $port;
return $node; return $node;
} }
...@@ -1528,8 +1424,9 @@ $stderr); ...@@ -1528,8 +1424,9 @@ $stderr);
=item $node->command_ok(...) =item $node->command_ok(...)
Runs a shell command like TestLib::command_ok, but with PGHOST and PGPORT set Runs a shell command like TestLib::command_ok, but with PGPORT
so that the command will default to connecting to this PostgresNode. set so that the command will default to connecting to this
PostgresNode.
=cut =cut
...@@ -1539,7 +1436,6 @@ sub command_ok ...@@ -1539,7 +1436,6 @@ sub command_ok
my $self = shift; my $self = shift;
local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port; local $ENV{PGPORT} = $self->port;
TestLib::command_ok(@_); TestLib::command_ok(@_);
...@@ -1550,7 +1446,7 @@ sub command_ok ...@@ -1550,7 +1446,7 @@ sub command_ok
=item $node->command_fails(...) =item $node->command_fails(...)
TestLib::command_fails with our connection parameters. See command_ok(...) TestLib::command_fails with our PGPORT. See command_ok(...)
=cut =cut
...@@ -1560,7 +1456,6 @@ sub command_fails ...@@ -1560,7 +1456,6 @@ sub command_fails
my $self = shift; my $self = shift;
local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port; local $ENV{PGPORT} = $self->port;
TestLib::command_fails(@_); TestLib::command_fails(@_);
...@@ -1571,7 +1466,7 @@ sub command_fails ...@@ -1571,7 +1466,7 @@ sub command_fails
=item $node->command_like(...) =item $node->command_like(...)
TestLib::command_like with our connection parameters. See command_ok(...) TestLib::command_like with our PGPORT. See command_ok(...)
=cut =cut
...@@ -1581,7 +1476,6 @@ sub command_like ...@@ -1581,7 +1476,6 @@ sub command_like
my $self = shift; my $self = shift;
local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port; local $ENV{PGPORT} = $self->port;
TestLib::command_like(@_); TestLib::command_like(@_);
...@@ -1592,8 +1486,7 @@ sub command_like ...@@ -1592,8 +1486,7 @@ sub command_like
=item $node->command_checks_all(...) =item $node->command_checks_all(...)
TestLib::command_checks_all with our connection parameters. See TestLib::command_checks_all with our PGPORT. See command_ok(...)
command_ok(...)
=cut =cut
...@@ -1603,7 +1496,6 @@ sub command_checks_all ...@@ -1603,7 +1496,6 @@ sub command_checks_all
my $self = shift; my $self = shift;
local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port; local $ENV{PGPORT} = $self->port;
TestLib::command_checks_all(@_); TestLib::command_checks_all(@_);
...@@ -1628,7 +1520,6 @@ sub issues_sql_like ...@@ -1628,7 +1520,6 @@ sub issues_sql_like
my ($self, $cmd, $expected_sql, $test_name) = @_; my ($self, $cmd, $expected_sql, $test_name) = @_;
local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port; local $ENV{PGPORT} = $self->port;
truncate $self->logfile, 0; truncate $self->logfile, 0;
...@@ -1643,8 +1534,8 @@ sub issues_sql_like ...@@ -1643,8 +1534,8 @@ sub issues_sql_like
=item $node->run_log(...) =item $node->run_log(...)
Runs a shell command like TestLib::run_log, but with connection parameters set Runs a shell command like TestLib::run_log, but with PGPORT set so
so that the command will default to connecting to this PostgresNode. that the command will default to connecting to this PostgresNode.
=cut =cut
...@@ -1652,7 +1543,6 @@ sub run_log ...@@ -1652,7 +1543,6 @@ sub run_log
{ {
my $self = shift; my $self = shift;
local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port; local $ENV{PGPORT} = $self->port;
TestLib::run_log(@_); TestLib::run_log(@_);
......
#
# Tests of pg_shmem.h functions
#
use strict;
use warnings;
use IPC::Run 'run';
use PostgresNode;
use Test::More;
use TestLib;
plan tests => 6;
my $tempdir = TestLib::tempdir;
my $port;
# Log "ipcs" diffs on a best-effort basis, swallowing any error.
my $ipcs_before = "$tempdir/ipcs_before";
eval { run_log [ 'ipcs', '-am' ], '>', $ipcs_before; };
sub log_ipcs
{
eval { run_log [ 'ipcs', '-am' ], '|', [ 'diff', $ipcs_before, '-' ] };
return;
}
# With Unix sockets, choose a port number such that the port number's first
# IpcMemoryKey candidate is available. If multiple copies of this test run
# concurrently, they will pick different ports. In the absence of collisions
# from other shmget() activity, gnat starts with key 0x7d001 (512001), and
# flea starts with key 0x7d002 (512002). With TCP, the first get_new_node
# picks a port number.
my $port_holder;
if (!$PostgresNode::use_tcp)
{
for ($port = 512; $port < 612; ++$port)
{
$port_holder = PostgresNode->get_new_node(
"port${port}_holder",
port => $port,
own_host => 1);
$port_holder->init;
$port_holder->start;
# Match the AddToDataDirLockFile() call in sysv_shmem.c. Assume all
# systems not using sysv_shmem.c do use TCP.
my $shmem_key_line_prefix = sprintf("%9lu ", 1 + $port * 1000);
last
if slurp_file($port_holder->data_dir . '/postmaster.pid') =~
/^$shmem_key_line_prefix/m;
$port_holder->stop;
}
}
# Node setup.
sub init_start
{
my $name = shift;
my $ret = PostgresNode->get_new_node($name, port => $port, own_host => 1);
defined($port) or $port = $ret->port; # same port for all nodes
$ret->init;
$ret->start;
log_ipcs();
return $ret;
}
my $gnat = init_start 'gnat';
my $flea = init_start 'flea';
# Upon postmaster death, postmaster children exit automatically.
$gnat->kill9;
log_ipcs();
$flea->restart; # flea ignores the shm key gnat abandoned.
log_ipcs();
poll_start($gnat); # gnat recycles its former shm key.
log_ipcs();
# After clean shutdown, the nodes swap shm keys.
$gnat->stop;
$flea->restart;
log_ipcs();
$gnat->start;
log_ipcs();
# Scenarios involving no postmaster.pid, dead postmaster, and a live backend.
# Use a regress.c function to emulate the responsiveness of a backend working
# through a CPU-intensive task.
$gnat->safe_psql('postgres', <<EOSQL);
CREATE FUNCTION wait_pid(int)
RETURNS void
AS '$ENV{REGRESS_SHLIB}'
LANGUAGE C STRICT;
EOSQL
my $slow_query = 'SELECT wait_pid(pg_backend_pid())';
my ($stdout, $stderr);
my $slow_client = IPC::Run::start(
[
'psql', '-X', '-qAt', '-d', $gnat->connstr('postgres'),
'-c', $slow_query
],
'<',
\undef,
'>',
\$stdout,
'2>',
\$stderr,
IPC::Run::timeout(900)); # five times the poll_query_until timeout
ok( $gnat->poll_query_until(
'postgres',
"SELECT 1 FROM pg_stat_activity WHERE query = '$slow_query'", '1'),
'slow query started');
my $slow_pid = $gnat->safe_psql('postgres',
"SELECT pid FROM pg_stat_activity WHERE query = '$slow_query'");
$gnat->kill9;
unlink($gnat->data_dir . '/postmaster.pid');
$gnat->rotate_logfile; # on Windows, can't open old log for writing
log_ipcs();
# Reject ordinary startup.
ok(!$gnat->start(fail_ok => 1), 'live query blocks restart');
like(
slurp_file($gnat->logfile),
qr/pre-existing shared memory block/,
'detected live backend via shared memory');
# Reject single-user startup.
my $single_stderr;
ok( !run_log(
[ 'postgres', '--single', '-D', $gnat->data_dir, 'template1' ],
'<', \('SELECT 1 + 1'), '2>', \$single_stderr),
'live query blocks --single');
print STDERR $single_stderr;
like(
$single_stderr,
qr/pre-existing shared memory block/,
'single-user mode detected live backend via shared memory');
log_ipcs();
# Fail to reject startup if shm key N has become available and we crash while
# using key N+1. This is unwanted, but expected. Windows is immune, because
# its GetSharedMemName() use DataDir strings, not numeric keys.
$flea->stop; # release first key
is( $gnat->start(fail_ok => 1),
$TestLib::windows_os ? 0 : 1,
'key turnover fools only sysv_shmem.c');
$gnat->stop; # release first key (no-op on $TestLib::windows_os)
$flea->start; # grab first key
# cleanup
TestLib::system_log('pg_ctl', 'kill', 'QUIT', $slow_pid);
$slow_client->finish; # client has detected backend termination
log_ipcs();
poll_start($gnat); # recycle second key
$gnat->stop;
$flea->stop;
$port_holder->stop if $port_holder;
log_ipcs();
# When postmaster children are slow to exit after postmaster death, we may
# need retries to start a new postmaster.
sub poll_start
{
my ($node) = @_;
my $max_attempts = 180 * 10;
my $attempts = 0;
while ($attempts < $max_attempts)
{
$node->start(fail_ok => 1) && return 1;
# Wait 0.1 second before retrying.
usleep(100_000);
$attempts++;
}
# No success within 180 seconds. Try one last time without fail_ok, which
# will BAIL_OUT unless it succeeds.
$node->start && return 1;
return 0;
}
...@@ -205,7 +205,6 @@ sub tap_check ...@@ -205,7 +205,6 @@ sub tap_check
local %ENV = %ENV; local %ENV = %ENV;
$ENV{PERL5LIB} = "$topdir/src/test/perl;$ENV{PERL5LIB}"; $ENV{PERL5LIB} = "$topdir/src/test/perl;$ENV{PERL5LIB}";
$ENV{PG_REGRESS} = "$topdir/$Config/pg_regress/pg_regress"; $ENV{PG_REGRESS} = "$topdir/$Config/pg_regress/pg_regress";
$ENV{REGRESS_SHLIB} = "$topdir/src/test/regress/regress.dll";
$ENV{TESTDIR} = "$dir"; $ENV{TESTDIR} = "$dir";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment