]> granicus.if.org Git - postgresql/commitdiff
Use data directory inode number, not port, to select SysV resource keys.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 5 Sep 2019 17:31:41 +0000 (13:31 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 5 Sep 2019 17:31:46 +0000 (13:31 -0400)
This approach provides a much tighter binding between a data directory
and the associated SysV shared memory block (and SysV or named-POSIX
semaphores, if we're using those).  Key collisions are still possible,
but only between data directories stored on different filesystems,
so the situation should be negligible in practice.  More importantly,
restarting the postmaster with a different port number no longer
risks failing to identify a relevant shared memory block, even when
postmaster.pid has been removed.  A standalone backend is likewise
much more certain to detect conflicting leftover backends.

(In the longer term, we might now think about deprecating the port as
a cluster-wide value, so that one postmaster could support sockets
with varying port numbers.  But that's for another day.)

The hazards fixed here apply only on Unix systems; our Windows code
paths already use identifiers derived from the data directory path
name rather than the port.

src/test/recovery/t/017_shm.pl, which intends to test key-collision
cases, has been substantially rewritten since it can no longer use
two postmasters with identical port numbers to trigger the case.
Instead, use Perl's IPC::SharedMem module to create a conflicting
shmem segment directly.  The test script will be skipped if that
module is not available.  (This means that some older buildfarm
members won't run it, but I don't think that that results in any
meaningful coverage loss.)

Patch by me; thanks to Noah Misch and Peter Eisentraut for discussion
and review.

Discussion: https://postgr.es/m/16908.1557521200@sss.pgh.pa.us

12 files changed:
src/backend/port/posix_sema.c
src/backend/port/sysv_sema.c
src/backend/port/sysv_shmem.c
src/backend/port/win32_sema.c
src/backend/port/win32_shmem.c
src/backend/postmaster/postmaster.c
src/backend/storage/ipc/ipci.c
src/backend/utils/init/postinit.c
src/include/storage/ipc.h
src/include/storage/pg_sema.h
src/include/storage/pg_shmem.h
src/test/recovery/t/017_shm.pl

index 3370adf35e27c87cc60734f118294533dcc922c4..bdd6552f6141576fd0213e2296d5ae75bc120096 100644 (file)
@@ -29,6 +29,7 @@
 #include <semaphore.h>
 #include <signal.h>
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "miscadmin.h"
 #include "storage/ipc.h"
@@ -181,10 +182,6 @@ PGSemaphoreShmemSize(int maxSemas)
  * are acquired here or in PGSemaphoreCreate, register an on_shmem_exit
  * callback to release them.
  *
- * The port number is passed for possible use as a key (for Posix, we use
- * it to generate the starting semaphore name).  In a standalone backend,
- * zero will be passed.
- *
  * In the Posix implementation, we acquire semaphores on-demand; the
  * maxSemas parameter is just used to size the arrays.  For unnamed
  * semaphores, there is an array of PGSemaphoreData structs in shared memory.
@@ -196,8 +193,22 @@ PGSemaphoreShmemSize(int maxSemas)
  * we don't have to expose the counters to other processes.)
  */
 void
-PGReserveSemaphores(int maxSemas, int port)
+PGReserveSemaphores(int maxSemas)
 {
+       struct stat statbuf;
+
+       /*
+        * We use the data directory's inode number to seed the search for free
+        * semaphore keys.  This minimizes the odds of collision with other
+        * postmasters, while maximizing the odds that we will detect and clean up
+        * semaphores left over from a crashed postmaster in our own directory.
+        */
+       if (stat(DataDir, &statbuf) < 0)
+               ereport(FATAL,
+                               (errcode_for_file_access(),
+                                errmsg("could not stat data directory \"%s\": %m",
+                                               DataDir)));
+
 #ifdef USE_NAMED_POSIX_SEMAPHORES
        mySemPointers = (sem_t **) malloc(maxSemas * sizeof(sem_t *));
        if (mySemPointers == NULL)
@@ -215,7 +226,7 @@ PGReserveSemaphores(int maxSemas, int port)
 
        numSems = 0;
        maxSems = maxSemas;
-       nextSemKey = port * 1000;
+       nextSemKey = statbuf.st_ino;
 
        on_shmem_exit(ReleaseSemaphores, 0);
 }
index ac5106f39daf03fa4fa6d46f6f7ae21760019722..a1652cce59c8c4b2bc4bde9106f33d669f0ba458 100644 (file)
@@ -17,6 +17,7 @@
 #include <signal.h>
 #include <unistd.h>
 #include <sys/file.h>
+#include <sys/stat.h>
 #ifdef HAVE_SYS_IPC_H
 #include <sys/ipc.h>
 #endif
@@ -301,10 +302,6 @@ PGSemaphoreShmemSize(int maxSemas)
  * are acquired here or in PGSemaphoreCreate, register an on_shmem_exit
  * callback to release them.
  *
- * The port number is passed for possible use as a key (for SysV, we use
- * it to generate the starting semaphore key).  In a standalone backend,
- * zero will be passed.
- *
  * In the SysV implementation, we acquire semaphore sets on-demand; the
  * maxSemas parameter is just used to size the arrays.  There is an array
  * of PGSemaphoreData structs in shared memory, and a postmaster-local array
@@ -314,8 +311,22 @@ PGSemaphoreShmemSize(int maxSemas)
  * have clobbered.)
  */
 void
-PGReserveSemaphores(int maxSemas, int port)
+PGReserveSemaphores(int maxSemas)
 {
+       struct stat statbuf;
+
+       /*
+        * We use the data directory's inode number to seed the search for free
+        * semaphore keys.  This minimizes the odds of collision with other
+        * postmasters, while maximizing the odds that we will detect and clean up
+        * semaphores left over from a crashed postmaster in our own directory.
+        */
+       if (stat(DataDir, &statbuf) < 0)
+               ereport(FATAL,
+                               (errcode_for_file_access(),
+                                errmsg("could not stat data directory \"%s\": %m",
+                                               DataDir)));
+
        /*
         * We must use ShmemAllocUnlocked(), since the spinlock protecting
         * ShmemAlloc() won't be ready yet.  (This ordering is necessary when we
@@ -332,7 +343,7 @@ PGReserveSemaphores(int maxSemas, int port)
        if (mySemaSets == NULL)
                elog(PANIC, "out of memory");
        numSemaSets = 0;
-       nextSemaKey = port * 1000;
+       nextSemaKey = statbuf.st_ino;
        nextSemaNumber = SEMAS_PER_SET; /* force sema set alloc on 1st call */
 
        on_shmem_exit(ReleaseSemaphores, 0);
index 968506dd51683240ca11febdab6e9163292046eb..dab2920ad61facf7a6dd7c0036a7cc804f7dbc68 100644 (file)
@@ -390,11 +390,12 @@ PGSharedMemoryAttach(IpcMemoryId shmId,
 
        /*
         * Try to attach to the segment and see if it matches our data directory.
-        * This avoids key-conflict problems on machines that are running several
-        * postmasters under the same userid and port number.  (That would not
-        * ordinarily happen in production, but it can happen during parallel
-        * testing.  Since our test setups don't open any TCP ports on Unix, such
-        * cases don't conflict otherwise.)
+        * This avoids any risk of duplicate-shmem-key conflicts on machines that
+        * are running several postmasters under the same userid.
+        *
+        * (When we're called from PGSharedMemoryCreate, this stat call is
+        * duplicative; but since this isn't a high-traffic case it's not worth
+        * trying to optimize.)
         */
        if (stat(DataDir, &statbuf) < 0)
                return SHMSTATE_ANALYSIS_FAILURE;       /* can't stat; be conservative */
@@ -617,12 +618,9 @@ AnonymousShmemDetach(int status, Datum arg)
  * we do not fail upon collision with foreign shmem segments.  The idea here
  * is to detect and re-use keys that may have been assigned by a crashed
  * postmaster or backend.
- *
- * The port number is passed for possible use as a key (for SysV, we use
- * it to generate the starting shmem key).
  */
 PGShmemHeader *
-PGSharedMemoryCreate(Size size, int port,
+PGSharedMemoryCreate(Size size,
                                         PGShmemHeader **shim)
 {
        IpcMemoryKey NextShmemSegID;
@@ -631,6 +629,17 @@ PGSharedMemoryCreate(Size size, int port,
        struct stat statbuf;
        Size            sysvsize;
 
+       /*
+        * We use the data directory's ID info (inode and device numbers) to
+        * positively identify shmem segments associated with this data dir, and
+        * also as seeds for searching for a free shmem key.
+        */
+       if (stat(DataDir, &statbuf) < 0)
+               ereport(FATAL,
+                               (errcode_for_file_access(),
+                                errmsg("could not stat data directory \"%s\": %m",
+                                               DataDir)));
+
        /* Complain if hugepages demanded but we can't possibly support them */
 #if !defined(MAP_HUGETLB)
        if (huge_pages == HUGE_PAGES_ON)
@@ -659,10 +668,10 @@ PGSharedMemoryCreate(Size size, int port,
        /*
         * Loop till we find a free IPC key.  Trust CreateDataDirLockFile() to
         * 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.)
+        * loop simultaneously.  (CreateDataDirLockFile() does not entirely ensure
+        * that, but prefer fixing it over coping here.)
         */
-       NextShmemSegID = 1 + port * 1000;
+       NextShmemSegID = statbuf.st_ino;
 
        for (;;)
        {
@@ -748,11 +757,6 @@ PGSharedMemoryCreate(Size size, int port,
        hdr->dsm_control = 0;
 
        /* Fill in the data directory ID info, too */
-       if (stat(DataDir, &statbuf) < 0)
-               ereport(FATAL,
-                               (errcode_for_file_access(),
-                                errmsg("could not stat data directory \"%s\": %m",
-                                               DataDir)));
        hdr->device = statbuf.st_dev;
        hdr->inode = statbuf.st_ino;
 
index 013c122451d20ef1f050bb420be00ec00b6aa3ef..32cc697866925126066bcf196ee29363ea3d2fe2 100644 (file)
@@ -44,7 +44,7 @@ PGSemaphoreShmemSize(int maxSemas)
  * process exits.
  */
 void
-PGReserveSemaphores(int maxSemas, int port)
+PGReserveSemaphores(int maxSemas)
 {
        mySemSet = (HANDLE *) malloc(maxSemas * sizeof(HANDLE));
        if (mySemSet == NULL)
index ccd7b6b5e33ec16f3137fa168394c492bf36850c..6cb63282847cf214f3137d7504944c9afa8e620c 100644 (file)
@@ -194,7 +194,7 @@ EnableLockPagesPrivilege(int elevel)
  * standard header.
  */
 PGShmemHeader *
-PGSharedMemoryCreate(Size size, int port,
+PGSharedMemoryCreate(Size size,
                                         PGShmemHeader **shim)
 {
        void       *memAddress;
index 62dc93d56bad892897846c4ce20a38a59d568f82..a5446d54bb5aa3a6371d2ef2694e0968ceb19bec 100644 (file)
@@ -385,7 +385,7 @@ static void getInstallationPaths(const char *argv0);
 static void checkControlFile(void);
 static Port *ConnCreate(int serverFd);
 static void ConnFree(Port *port);
-static void reset_shared(int port);
+static void reset_shared(void);
 static void SIGHUP_handler(SIGNAL_ARGS);
 static void pmdie(SIGNAL_ARGS);
 static void reaper(SIGNAL_ARGS);
@@ -1175,7 +1175,7 @@ PostmasterMain(int argc, char *argv[])
        /*
         * Set up shared memory and semaphores.
         */
-       reset_shared(PostPortNumber);
+       reset_shared();
 
        /*
         * Estimate number of openable files.  This must happen after setting up
@@ -2599,17 +2599,16 @@ InitProcessGlobals(void)
  * reset_shared -- reset shared memory and semaphores
  */
 static void
-reset_shared(int port)
+reset_shared(void)
 {
        /*
         * Create or re-create shared memory and semaphores.
         *
         * Note: in each "cycle of life" we will normally assign the same IPC keys
-        * (if using SysV shmem and/or semas), since the port number is used to
-        * determine IPC keys.  This helps ensure that we will clean up dead IPC
-        * objects if the postmaster crashes and is restarted.
+        * (if using SysV shmem and/or semas).  This helps ensure that we will
+        * clean up dead IPC objects if the postmaster crashes and is restarted.
         */
-       CreateSharedMemoryAndSemaphores(port);
+       CreateSharedMemoryAndSemaphores();
 }
 
 
@@ -3934,7 +3933,7 @@ PostmasterStateMachine(void)
                /* re-read control file into local memory */
                LocalProcessControlFile(true);
 
-               reset_shared(PostPortNumber);
+               reset_shared();
 
                StartupPID = StartupDataBase();
                Assert(StartupPID != 0);
@@ -4962,7 +4961,7 @@ SubPostmasterMain(int argc, char *argv[])
                InitProcess();
 
                /* Attach process to shared data structures */
-               CreateSharedMemoryAndSemaphores(0);
+               CreateSharedMemoryAndSemaphores();
 
                /* And run the backend */
                BackendRun(&port);              /* does not return */
@@ -4976,7 +4975,7 @@ SubPostmasterMain(int argc, char *argv[])
                InitAuxiliaryProcess();
 
                /* Attach process to shared data structures */
-               CreateSharedMemoryAndSemaphores(0);
+               CreateSharedMemoryAndSemaphores();
 
                AuxiliaryProcessMain(argc - 2, argv + 2);       /* does not return */
        }
@@ -4989,7 +4988,7 @@ SubPostmasterMain(int argc, char *argv[])
                InitProcess();
 
                /* Attach process to shared data structures */
-               CreateSharedMemoryAndSemaphores(0);
+               CreateSharedMemoryAndSemaphores();
 
                AutoVacLauncherMain(argc - 2, argv + 2);        /* does not return */
        }
@@ -5002,7 +5001,7 @@ SubPostmasterMain(int argc, char *argv[])
                InitProcess();
 
                /* Attach process to shared data structures */
-               CreateSharedMemoryAndSemaphores(0);
+               CreateSharedMemoryAndSemaphores();
 
                AutoVacWorkerMain(argc - 2, argv + 2);  /* does not return */
        }
@@ -5020,7 +5019,7 @@ SubPostmasterMain(int argc, char *argv[])
                InitProcess();
 
                /* Attach process to shared data structures */
-               CreateSharedMemoryAndSemaphores(0);
+               CreateSharedMemoryAndSemaphores();
 
                /* Fetch MyBgworkerEntry from shared memory */
                shmem_slot = atoi(argv[1] + 15);
index d7d733530ff740216ab9c27f50c5a218b61d4536..885370698fca8edc4674ea9e54d23e3c639b7821 100644 (file)
@@ -91,7 +91,7 @@ RequestAddinShmemSpace(Size size)
  * This is a bit code-wasteful and could be cleaned up.)
  */
 void
-CreateSharedMemoryAndSemaphores(int port)
+CreateSharedMemoryAndSemaphores(void)
 {
        PGShmemHeader *shim = NULL;
 
@@ -163,14 +163,14 @@ CreateSharedMemoryAndSemaphores(int port)
                /*
                 * Create the shmem segment
                 */
-               seghdr = PGSharedMemoryCreate(size, port, &shim);
+               seghdr = PGSharedMemoryCreate(size, &shim);
 
                InitShmemAccess(seghdr);
 
                /*
                 * Create semaphores
                 */
-               PGReserveSemaphores(numSemas, port);
+               PGReserveSemaphores(numSemas);
 
                /*
                 * If spinlocks are disabled, initialize emulation layer (which
index 43b9f17f722698937e7569d5dde0d37fe30c1991..29c5ec7b58bfbbd166afe12be6b42167b134c7a9 100644 (file)
@@ -445,12 +445,10 @@ InitCommunication(void)
        if (!IsUnderPostmaster)         /* postmaster already did this */
        {
                /*
-                * We're running a postgres bootstrap process or a standalone backend.
-                * Though we won't listen on PostPortNumber, use it to select a shmem
-                * key.  This increases the chance of detecting a leftover live
-                * backend of this DataDir.
+                * We're running a postgres bootstrap process or a standalone backend,
+                * so we need to set up shmem.
                 */
-               CreateSharedMemoryAndSemaphores(PostPortNumber);
+               CreateSharedMemoryAndSemaphores();
        }
 }
 
index e9b243fe7d4fb586f3d94e42fe0d30107dc077be..d113813df32ad2c807e5f9a8e5989d5930c27d39 100644 (file)
@@ -76,6 +76,6 @@ extern void on_exit_reset(void);
 /* ipci.c */
 extern PGDLLIMPORT shmem_startup_hook_type shmem_startup_hook;
 
-extern void CreateSharedMemoryAndSemaphores(int port);
+extern void CreateSharedMemoryAndSemaphores(void);
 
 #endif                                                 /* IPC_H */
index b95efa1e22e1390c0c62043031ca269bb340136d..1ac4f9a830f0a9348d5b5bb9aa4a94281d301ce0 100644 (file)
@@ -41,7 +41,7 @@ typedef HANDLE PGSemaphore;
 extern Size PGSemaphoreShmemSize(int maxSemas);
 
 /* Module initialization (called during postmaster start or shmem reinit) */
-extern void PGReserveSemaphores(int maxSemas, int port);
+extern void PGReserveSemaphores(int maxSemas);
 
 /* Allocate a PGSemaphore structure with initial count 1 */
 extern PGSemaphore PGSemaphoreCreate(void);
index ac24878efbfeb75bf004117b3a9dc77e6fef6d9a..6bd566405b23376db5c1f2318cc2e83eaf1fac22 100644 (file)
@@ -82,7 +82,7 @@ extern void PGSharedMemoryReAttach(void);
 extern void PGSharedMemoryNoReAttach(void);
 #endif
 
-extern PGShmemHeader *PGSharedMemoryCreate(Size size, int port,
+extern PGShmemHeader *PGSharedMemoryCreate(Size size,
                                                                                   PGShmemHeader **shim);
 extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2);
 extern void PGSharedMemoryDetach(void);
index 7f10ff5225b13af55fb3c8c29f36b2114ff056bd..a29ef788551896a25b6bb06484b3ed12a165f87d 100644 (file)
@@ -4,16 +4,30 @@
 use strict;
 use warnings;
 use Config;
+use File::stat qw(stat);
 use IPC::Run 'run';
 use PostgresNode;
 use Test::More;
 use TestLib;
 use Time::HiRes qw(usleep);
 
-plan tests => 5;
+# If we don't have shmem support, skip the whole thing
+eval {
+       require IPC::SharedMem;
+       IPC::SharedMem->import;
+       require IPC::SysV;
+       IPC::SysV->import(qw(IPC_CREAT IPC_EXCL S_IRUSR S_IWUSR));
+};
+if ($@)
+{
+       plan skip_all => 'SysV shared memory not supported by this platform';
+}
+else
+{
+       plan tests => 4;
+}
 
 my $tempdir = TestLib::tempdir;
-my $port;
 
 # Log "ipcs" diffs on a best-effort basis, swallowing any error.
 my $ipcs_before = "$tempdir/ipcs_before";
@@ -25,76 +39,80 @@ sub log_ipcs
        return;
 }
 
-# These tests need a $port such that nothing creates or removes a segment in
-# $port's IpcMemoryKey range while this test script runs.  While there's no
-# way to ensure that in general, we do ensure that if PostgreSQL tests are the
-# only actors.  With TCP, the first get_new_node picks a port number.  With
-# Unix sockets, use a postmaster, $port_holder, to represent a key space
-# reservation.  $port_holder holds a reservation on the key space of port
-# 1+$port_holder->port if it created the first IpcMemoryKey of its own port's
-# key space.  If multiple copies of this test script run concurrently, they
-# will pick different ports.  $port_holder postmasters use odd-numbered ports,
-# and tests use even-numbered ports.  In the absence of collisions from other
-# shmget() activity, gnat starts with key 0x7d001 (512001), and flea starts
-# with key 0x7d002 (512002).
-my $port_holder;
-if (!$PostgresNode::use_tcp)
-{
-       my $lock_port;
-       for ($lock_port = 511; $lock_port < 711; $lock_port += 2)
-       {
-               $port_holder = PostgresNode->get_new_node(
-                       "port${lock_port}_holder",
-                       port     => $lock_port,
-                       own_host => 1);
-               $port_holder->init;
-               $port_holder->append_conf('postgresql.conf', 'max_connections = 5');
-               $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 + $lock_port * 1000);
-               last
-                 if slurp_file($port_holder->data_dir . '/postmaster.pid') =~
-                 /^$shmem_key_line_prefix/m;
-               $port_holder->stop;
-       }
-       $port = $lock_port + 1;
-}
-
 # 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;
-       # Limit semaphore consumption, since we run several nodes concurrently.
-       $ret->append_conf('postgresql.conf', 'max_connections = 5');
-       $ret->start;
-       log_ipcs();
-       return $ret;
-}
-my $gnat = init_start 'gnat';
-my $flea = init_start 'flea';
+my $gnat = PostgresNode->get_new_node('gnat');
+$gnat->init;
+
+# Create a shmem segment that will conflict with gnat's first choice
+# of shmem key.  (If we fail to create it because something else is
+# already using that key, that's perfectly fine, though the test will
+# exercise a different scenario than it usually does.)
+my $gnat_dir_stat = stat($gnat->data_dir);
+defined($gnat_dir_stat) or die('unable to stat ' . $gnat->data_dir);
+my $gnat_inode = $gnat_dir_stat->ino;
+note "gnat's datadir inode = $gnat_inode";
+
+# Note: must reference IPC::SysV's constants as functions, or this file
+# fails to compile when that module is not available.
+my $gnat_conflict_shm =
+  IPC::SharedMem->new($gnat_inode, 1024,
+       IPC_CREAT() | IPC_EXCL() | S_IRUSR() | S_IWUSR());
+note "could not create conflicting shmem" if !defined($gnat_conflict_shm);
+log_ipcs();
+
+$gnat->start;
+log_ipcs();
+
+$gnat->restart;    # should keep same shmem key
+log_ipcs();
 
 # 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.
+note "removing the conflicting shmem ...";
+$gnat_conflict_shm->remove if $gnat_conflict_shm;
+log_ipcs();
+
+# Upon postmaster death, postmaster children exit automatically.
+$gnat->kill9;
+log_ipcs();
+
+# In this start, gnat will use its normal shmem key, and fail to remove
+# the higher-keyed segment that the previous postmaster was using.
+# That's not great, but key collisions should be rare enough to not
+# make this a big problem.
+poll_start($gnat);
+log_ipcs();
 $gnat->stop;
-$flea->restart;
 log_ipcs();
+
+# Re-create the conflicting segment, and start/stop normally, just so
+# this test script doesn't leak the higher-keyed segment.
+note "re-creating conflicting shmem ...";
+$gnat_conflict_shm =
+  IPC::SharedMem->new($gnat_inode, 1024,
+       IPC_CREAT() | IPC_EXCL() | S_IRUSR() | S_IWUSR());
+note "could not create conflicting shmem" if !defined($gnat_conflict_shm);
+log_ipcs();
+
 $gnat->start;
 log_ipcs();
+$gnat->stop;
+log_ipcs();
+
+note "removing the conflicting shmem ...";
+$gnat_conflict_shm->remove if $gnat_conflict_shm;
+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->start;
+log_ipcs();
+
 my $regress_shlib = TestLib::perl2host($ENV{REGRESS_SHLIB});
 $gnat->safe_psql('postgres', <<EOSQL);
 CREATE FUNCTION wait_pid(int)
@@ -152,24 +170,18 @@ print STDERR $single_stderr;
 like($single_stderr, $pre_existing_msg,
        '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
+
+# cleanup slow backend
 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
 
+# now startup should work
+poll_start($gnat);
+log_ipcs();
+
+# finish testing
 $gnat->stop;
-$flea->stop;
-$port_holder->stop if $port_holder;
 log_ipcs();