Revert "Consistently test for in-use shared memory."
This reverts commits 2f932f71d9f2963bbd201129d7b971c8f5f077fd, 16ee6eaf80a40007a138b60bb5661660058d0422 and 6f0e190056fe441f7cf788ff19b62b13c94f68f3. The buildfarm has revealed several bugs. Back-patch like the original commits. Discussion: https://postgr.es/m/20190404145319.GA1720877@rfd.leadboat.com
This commit is contained in:
parent
794c543b17
commit
82150a05be
@ -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 @@
|
|||||||
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;
|
|||||||
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)
|
|||||||
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,
|
|||||||
* 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,
|
|||||||
* 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,
|
|||||||
*/
|
*/
|
||||||
#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,
|
|||||||
* 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,
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
* 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,
|
|||||||
/* 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,
|
|||||||
|
|
||||||
/* 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
|
||||||
|
* to someone else after all, and continue quietly.
|
||||||
*/
|
*/
|
||||||
elog(LOG,
|
if (hdr->dsm_control != 0)
|
||||||
"shared memory block (key %lu, ID %lu) deleted during startup",
|
dsm_cleanup_using_control_segment(hdr->dsm_control);
|
||||||
(unsigned long) NextShmemSegID,
|
shmdt(memAddress);
|
||||||
(unsigned long) shmid);
|
|
||||||
break;
|
|
||||||
case SHMSTATE_FOREIGN:
|
|
||||||
NextShmemSegID++;
|
|
||||||
break;
|
|
||||||
case SHMSTATE_UNATTACHED:
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The segment pertains to DataDir, and every process that had
|
|
||||||
* used it has died or detached. Zap it, if possible, and any
|
|
||||||
* associated dynamic shared memory segments, as well. This
|
|
||||||
* shouldn't fail, but if it does, assume the segment belongs
|
|
||||||
* to someone else after all, and try the next candidate.
|
|
||||||
* 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)
|
if (shmctl(shmid, IPC_RMID, NULL) < 0)
|
||||||
NextShmemSegID++;
|
continue;
|
||||||
break;
|
|
||||||
|
/*
|
||||||
|
* Now try again to create the segment.
|
||||||
|
*/
|
||||||
|
memAddress = InternalIpcMemoryCreate(NextShmemSegID, sysvsize);
|
||||||
|
if (memAddress)
|
||||||
|
break; /* successful create and attach */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldhdr && shmdt(oldhdr) < 0)
|
/*
|
||||||
elog(LOG, "shmdt(%p) failed: %m", oldhdr);
|
* 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!
|
||||||
/* Initialize new segment. */
|
*/
|
||||||
hdr = (PGShmemHeader *) memAddress;
|
hdr = (PGShmemHeader *) memAddress;
|
||||||
hdr->creatorPID = getpid();
|
hdr->creatorPID = getpid();
|
||||||
hdr->magic = PGShmemMagic;
|
hdr->magic = PGShmemMagic;
|
||||||
@ -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)
|
|||||||
#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)
|
|||||||
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)
|
|||||||
*
|
*
|
||||||
* 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)
|
|||||||
* 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[])
|
|||||||
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[])
|
|||||||
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[])
|
|||||||
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[])
|
|||||||
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[])
|
|||||||
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)
|
|||||||
* 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)
|
|||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* 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);
|
|||||||
/* 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 */
|
|||||||
{
|
{
|
||||||
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);
|
|||||||
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(
|
|||||||
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,12 +118,11 @@ 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";
|
||||||
$last_host_assigned = 1;
|
$test_pghost =
|
||||||
$test_pghost = $use_tcp ? $test_localhost : TestLib::tempdir_short;
|
$TestLib::windows_os ? $test_localhost : TestLib::tempdir_short;
|
||||||
$ENV{PGHOST} = $test_pghost;
|
$ENV{PGHOST} = $test_pghost;
|
||||||
$ENV{PGDATABASE} = 'postgres';
|
$ENV{PGDATABASE} = 'postgres';
|
||||||
|
|
||||||
@ -157,8 +155,6 @@ 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_base => "$TestLib::log_path/${testname}_${name}",
|
|
||||||
_logfile => "$TestLib::log_path/${testname}_${name}.log"
|
_logfile => "$TestLib::log_path/${testname}_${name}.log"
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -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
|
|||||||
{
|
{
|
||||||
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
|
|||||||
{
|
{
|
||||||
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
|
|||||||
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
|
|||||||
|
|
||||||
=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
|
|||||||
{
|
{
|
||||||
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
|
|||||||
|
|
||||||
=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.
|
|||||||
|
|
||||||
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>.
|
|||||||
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
|
|||||||
$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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|||||||
|
|
||||||
=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
|
|||||||
|
|
||||||
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
|
|||||||
|
|
||||||
=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
|
|||||||
|
|
||||||
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
|
|||||||
|
|
||||||
=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
|
|||||||
|
|
||||||
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
|
|||||||
|
|
||||||
=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
|
|||||||
|
|
||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
|
|
||||||
=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
|
|||||||
{
|
{
|
||||||
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(@_);
|
||||||
|
@ -1,177 +0,0 @@
|
|||||||
#
|
|
||||||
# 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
|
|||||||
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";
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user