Commit c0985099 authored by Noah Misch's avatar Noah Misch

Consistently test for in-use shared memory.

postmaster startup scrutinizes any shared memory segment recorded in
postmaster.pid, exiting if that segment matches the current data
directory and has an attached process.  When the postmaster.pid file was
missing, a starting postmaster used weaker checks.  Change to use the
same checks in both scenarios.  This increases the chance of a startup
failure, in lieu of data corruption, if the DBA does "kill -9 `head -n1
postmaster.pid` && rm postmaster.pid && pg_ctl -w start".  A postmaster
will no longer stop if shmat() of an old segment fails with EACCES.  A
postmaster will no longer recycle segments pertaining to other data
directories.  That's good for production, but it's bad for integration
tests that crash a postmaster and immediately delete its data directory.
Such a test now leaks a segment indefinitely.  No "make check-world"
test does that.  win32_shmem.c already avoided all these problems.  In
9.6 and later, enhance PostgresNode to facilitate testing.  Back-patch
to 9.4 (all supported versions).

Reviewed (in earlier versions) by Daniel Gustafsson and Kyotaro HORIGUCHI.

Discussion: https://postgr.es/m/20190408064141.GA2016666@rfd.leadboat.com
parent db8db624
...@@ -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' $(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' REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' $(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' $(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' REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
endef endef
else else
......
This diff is collapsed.
...@@ -192,14 +192,9 @@ EnableLockPagesPrivilege(int elevel) ...@@ -192,14 +192,9 @@ 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, bool makePrivate, int port, PGSharedMemoryCreate(Size size, int port,
PGShmemHeader **shim) PGShmemHeader **shim)
{ {
void *memAddress; void *memAddress;
......
...@@ -2605,7 +2605,7 @@ reset_shared(int port) ...@@ -2605,7 +2605,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(false, port); CreateSharedMemoryAndSemaphores(port);
} }
...@@ -4946,7 +4946,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -4946,7 +4946,7 @@ SubPostmasterMain(int argc, char *argv[])
InitProcess(); InitProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(false, 0); CreateSharedMemoryAndSemaphores(0);
/* And run the backend */ /* And run the backend */
BackendRun(&port); /* does not return */ BackendRun(&port); /* does not return */
...@@ -4960,7 +4960,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -4960,7 +4960,7 @@ SubPostmasterMain(int argc, char *argv[])
InitAuxiliaryProcess(); InitAuxiliaryProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(false, 0); CreateSharedMemoryAndSemaphores(0);
AuxiliaryProcessMain(argc - 2, argv + 2); /* does not return */ AuxiliaryProcessMain(argc - 2, argv + 2); /* does not return */
} }
...@@ -4973,7 +4973,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -4973,7 +4973,7 @@ SubPostmasterMain(int argc, char *argv[])
InitProcess(); InitProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(false, 0); CreateSharedMemoryAndSemaphores(0);
AutoVacLauncherMain(argc - 2, argv + 2); /* does not return */ AutoVacLauncherMain(argc - 2, argv + 2); /* does not return */
} }
...@@ -4986,7 +4986,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -4986,7 +4986,7 @@ SubPostmasterMain(int argc, char *argv[])
InitProcess(); InitProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(false, 0); CreateSharedMemoryAndSemaphores(0);
AutoVacWorkerMain(argc - 2, argv + 2); /* does not return */ AutoVacWorkerMain(argc - 2, argv + 2); /* does not return */
} }
...@@ -5004,7 +5004,7 @@ SubPostmasterMain(int argc, char *argv[]) ...@@ -5004,7 +5004,7 @@ SubPostmasterMain(int argc, char *argv[])
InitProcess(); InitProcess();
/* Attach process to shared data structures */ /* Attach process to shared data structures */
CreateSharedMemoryAndSemaphores(false, 0); CreateSharedMemoryAndSemaphores(0);
/* Fetch MyBgworkerEntry from shared memory */ /* Fetch MyBgworkerEntry from shared memory */
shmem_slot = atoi(argv[1] + 15); shmem_slot = atoi(argv[1] + 15);
......
...@@ -89,12 +89,9 @@ RequestAddinShmemSpace(Size size) ...@@ -89,12 +89,9 @@ 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(bool makePrivate, int port) CreateSharedMemoryAndSemaphores(int port)
{ {
PGShmemHeader *shim = NULL; PGShmemHeader *shim = NULL;
...@@ -166,7 +163,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port) ...@@ -166,7 +163,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
/* /*
* Create the shmem segment * Create the shmem segment
*/ */
seghdr = PGSharedMemoryCreate(size, makePrivate, port, &shim); seghdr = PGSharedMemoryCreate(size, port, &shim);
InitShmemAccess(seghdr); InitShmemAccess(seghdr);
...@@ -187,12 +184,9 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port) ...@@ -187,12 +184,9 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, 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, and even then only * should only be reached in the EXEC_BACKEND case.
* with makePrivate == false.
*/ */
#ifdef EXEC_BACKEND #ifndef 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,9 +446,11 @@ InitCommunication(void) ...@@ -446,9 +446,11 @@ InitCommunication(void)
{ {
/* /*
* We're running a postgres bootstrap process or a standalone backend. * We're running a postgres bootstrap process or a standalone backend.
* Create private "shmem" and semaphores. * 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.
*/ */
CreateSharedMemoryAndSemaphores(true, 0); CreateSharedMemoryAndSemaphores(PostPortNumber);
} }
} }
......
...@@ -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(bool makePrivate, int port); extern void CreateSharedMemoryAndSemaphores(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 */ pid_t creatorPID; /* PID of creating process (set but unread) */
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 */
...@@ -82,8 +82,8 @@ extern void PGSharedMemoryReAttach(void); ...@@ -82,8 +82,8 @@ extern void PGSharedMemoryReAttach(void);
extern void PGSharedMemoryNoReAttach(void); extern void PGSharedMemoryNoReAttach(void);
#endif #endif
extern PGShmemHeader *PGSharedMemoryCreate(Size size, bool makePrivate, extern PGShmemHeader *PGSharedMemoryCreate(Size size, int port,
int port, PGShmemHeader **shim); 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);
......
This diff is collapsed.
#
# Tests of pg_shmem.h functions
#
use strict;
use warnings;
use IPC::Run 'run';
use PostgresNode;
use Test::More;
use TestLib;
use Time::HiRes qw(usleep);
plan tests => 5;
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;
}
# 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';
# 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. Retry for the same reasons poll_start() does.
my $pre_existing_msg = qr/pre-existing shared memory block/;
{
my $max_attempts = 180 * 10; # Retry every 0.1s for at least 180s.
my $attempts = 0;
while ($attempts < $max_attempts)
{
last
if $gnat->start(fail_ok => 1)
|| slurp_file($gnat->logfile) =~ $pre_existing_msg;
usleep(100_000);
$attempts++;
}
}
like(slurp_file($gnat->logfile),
$pre_existing_msg, '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, $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
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();
# We may need retries to start a new postmaster. Causes:
# - kernel is slow to deliver SIGKILL
# - postmaster parent is slow to waitpid()
# - postmaster child is slow to exit in response to SIGQUIT
# - postmaster child is slow to exit after postmaster death
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,6 +205,7 @@ sub tap_check ...@@ -205,6 +205,7 @@ 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