On Wed, Jun 21, 2017 at 06:44:09PM -0400, Tom Lane wrote:
> Today, lorikeet failed with a new variant on the bgworker start crash:
>
> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=lorikeet&dt=2017-06-21%2020%3A29%3A10
>
> This one is even more exciting than the last one, because it sure looks
> like the crashing bgworker took the postmaster down with it. That is
> Not Supposed To Happen.
>
> Wondering if we broke something here recently, I tried to reproduce it
> on a Linux machine by adding a randomized Assert failure in
> shm_mq_set_sender. I don't see any such problem, even with EXEC_BACKEND;
> we recover from the crash as-expected.
>
> So I'm starting to get a distinct feeling that there's something wrong
> with the cygwin port. But I dunno what.
I think signal blocking broke on Cygwin.
On a system (gcc 5.4.0, CYGWIN_NT-10.0 2.7.0(0.306/5/3) 2017-02-12 13:18
x86_64) that reproduces lorikeet's symptoms, I instrumented the postmaster as
attached. The patch's small_parallel.sql is a subset of select_parallel.sql
sufficient to reproduce the mq_sender Assert failure and the postmaster silent
exit. (It occasionally needed hundreds of iterations to do so.) The parallel
query normally starts four bgworkers; when the mq_sender Assert fired, the
test had started five workers in response to four registrations.
The postmaster.c instrumentation regularly detects sigusr1_handler() calls
while another sigusr1_handler() is already on the stack:
6328 2017-08-02 07:25:42.788 GMT LOG: forbid signals @ sigusr1_handler
6328 2017-08-02 07:25:42.788 GMT DEBUG: saw slot-0 registration, want 0
6328 2017-08-02 07:25:42.788 GMT DEBUG: saw slot-0 registration, want 1
6328 2017-08-02 07:25:42.788 GMT DEBUG: slot 1 not yet registered
6328 2017-08-02 07:25:42.789 GMT DEBUG: registering background worker
"parallel worker for PID 4776" (slot 1)
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-1 registration, want 2
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-0 registration, want 2
6328 2017-08-02 07:25:42.789 GMT DEBUG: slot 2 not yet registered
6328 2017-08-02 07:25:42.789 GMT DEBUG: registering background worker
"parallel worker for PID 4776" (slot 2)
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-2 registration, want 3
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-1 registration, want 3
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-0 registration, want 3
6328 2017-08-02 07:25:42.789 GMT DEBUG: slot 3 not yet registered
6328 2017-08-02 07:25:42.789 GMT DEBUG: registering background worker
"parallel worker for PID 4776" (slot 3)
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-3 registration, want 4
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-2 registration, want 4
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-1 registration, want 4
6328 2017-08-02 07:25:42.789 GMT DEBUG: saw slot-0 registration, want 4
6328 2017-08-02 07:25:42.789 GMT DEBUG: slot 4 not yet registered
6328 2017-08-02 07:25:42.789 GMT DEBUG: registering background worker
"parallel worker for PID 4776" (slot 4)
6328 2017-08-02 07:25:42.789 GMT DEBUG: starting background worker process
"parallel worker for PID 4776"
6328 2017-08-02 07:25:42.790 GMT LOG: forbid signals @ sigusr1_handler
6328 2017-08-02 07:25:42.790 GMT WARNING: signals already forbidden @
sigusr1_handler
6328 2017-08-02 07:25:42.790 GMT LOG: permit signals @ sigusr1_handler
postmaster algorithms rely on the PG_SETMASK() calls preventing that. Without
such protection, duplicate bgworkers are an understandable result. I caught
several other assertions; the PMChildFlags failure is another case of
duplicate postmaster children:
6 TRAP: FailedAssertion("!(entry->trans == ((void *)0))", File:
"pgstat.c", Line: 871)
3 TRAP: FailedAssertion("!(PMSignalState->PMChildFlags[slot] == 1)",
File: "pmsignal.c", Line: 229)
20 TRAP: FailedAssertion("!(RefCountErrors == 0)", File: "bufmgr.c", Line:
2523)
21 TRAP: FailedAssertion("!(vmq->mq_sender == ((void *)0))", File:
"shm_mq.c", Line: 221)
Also, got a few "select() failed in postmaster: Bad address"
I suspect a Cygwin signals bug. I'll try to distill a self-contained test
case for the Cygwin hackers. The lack of failures on buildfarm member brolga
argues that older Cygwin is not affected.
diff --git a/src/backend/access/transam/parallel.c
b/src/backend/access/transam/parallel.c
index 17b1038..f42034d 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -985,6 +985,8 @@ ParallelWorkerMain(Datum main_arg)
error_queue_space = shm_toc_lookup(toc, PARALLEL_KEY_ERROR_QUEUE,
false);
mq = (shm_mq *) (error_queue_space +
ParallelWorkerNumber *
PARALLEL_ERROR_QUEUE_SIZE);
+ write_stderr("PID %d claiming queue %d (%p)\n",
+ MyProc->pid, ParallelWorkerNumber, mq);
shm_mq_set_sender(mq, MyProc);
mqh = shm_mq_attach(mq, seg, NULL);
pq_redirect_to_shm_mq(seg, mqh);
diff --git a/src/backend/postmaster/bgworker.c
b/src/backend/postmaster/bgworker.c
index 28af6f0..e393d31 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -219,14 +219,18 @@ FindRegisteredWorkerBySlotNumber(int slotno)
{
slist_iter siter;
+ /* elog(DEBUG1, "looking for slot-%d registration", slotno); */
slist_foreach(siter, &BackgroundWorkerList)
{
RegisteredBgWorker *rw;
rw = slist_container(RegisteredBgWorker, rw_lnode, siter.cur);
+ elog(DEBUG1, "saw slot-%d registration, want %d",
+ rw->rw_shmem_slot, slotno);
if (rw->rw_shmem_slot == slotno)
return rw;
}
+ /* elog(DEBUG1, "did not find slot-%d registration", slotno); */
return NULL;
}
@@ -297,6 +301,7 @@ BackgroundWorkerStateChange(void)
}
continue;
}
+ elog(DEBUG1, "slot %d not yet registered", slotno);
/*
* If the worker is marked for termination, we don't need to
add it to
@@ -390,8 +395,8 @@ BackgroundWorkerStateChange(void)
/* Log it! */
ereport(DEBUG1,
- (errmsg("registering background worker \"%s\"",
- rw->rw_worker.bgw_name)));
+ (errmsg("registering background worker \"%s\"
(slot %d)",
+ rw->rw_worker.bgw_name,
rw->rw_shmem_slot)));
slist_push_head(&BackgroundWorkerList, &rw->rw_lnode);
}
@@ -422,8 +427,8 @@ ForgetBackgroundWorker(slist_mutable_iter *cur)
slot->in_use = false;
ereport(DEBUG1,
- (errmsg("unregistering background worker \"%s\"",
- rw->rw_worker.bgw_name)));
+ (errmsg("unregistering background worker \"%s\" (slot
%d)",
+ rw->rw_worker.bgw_name,
rw->rw_shmem_slot)));
slist_delete_current(cur);
free(rw);
diff --git a/src/backend/postmaster/postmaster.c
b/src/backend/postmaster/postmaster.c
index 83e99b7..5fb5db1 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -561,6 +561,34 @@ int postmaster_alive_fds[2] = {-1,
-1};
HANDLE PostmasterHandle;
#endif
+static bool siglevel = false;
+static void sigforbid(const char *location)
+{
+ int save_errno;
+
+ PG_SETMASK(&BlockSig);
+
+ save_errno = errno;
+ elog(LOG, "forbid signals @ %s", location);
+ if (siglevel)
+ elog(WARNING, "signals already forbidden @ %s", location);
+ siglevel = true;
+ errno = save_errno;
+}
+static void sigpermit(const char *location)
+{
+ int save_errno;
+
+ save_errno = errno;
+ elog(LOG, "permit signals @ %s", location);
+ if (!siglevel)
+ elog(WARNING, "signals already permitted @ %s", location);
+ siglevel = false;
+ errno = save_errno;
+
+ PG_SETMASK(&UnBlockSig);
+}
+
/*
* Postmaster main entry point
*/
@@ -629,7 +657,7 @@ PostmasterMain(int argc, char *argv[])
* postmaster/checkpointer.c.
*/
pqinitmask();
- PG_SETMASK(&BlockSig);
+ sigforbid(__FUNCTION__);
pqsignal_no_restart(SIGHUP, SIGHUP_handler); /* reread config file
and
* have children do same */
@@ -1686,12 +1714,12 @@ ServerLoop(void)
if (pmState == PM_WAIT_DEAD_END)
{
- PG_SETMASK(&UnBlockSig);
+ sigpermit("PM_WAIT_DEAD_END");
pg_usleep(100000L); /* 100 msec seems reasonable */
selres = 0;
- PG_SETMASK(&BlockSig);
+ sigforbid("PM_WAIT_DEAD_END");
}
else
{
@@ -1701,11 +1729,11 @@ ServerLoop(void)
/* Needs to run with blocked signals! */
DetermineSleepTime(&timeout);
- PG_SETMASK(&UnBlockSig);
+ sigpermit("select");
selres = select(nSockets, &rmask, NULL, NULL, &timeout);
- PG_SETMASK(&BlockSig);
+ sigforbid("select");
}
/* Now check the select() result */
@@ -2507,7 +2535,7 @@ SIGHUP_handler(SIGNAL_ARGS)
{
int save_errno = errno;
- PG_SETMASK(&BlockSig);
+ sigforbid(__FUNCTION__);
if (Shutdown <= SmartShutdown)
{
@@ -2566,7 +2594,7 @@ SIGHUP_handler(SIGNAL_ARGS)
#endif
}
- PG_SETMASK(&UnBlockSig);
+ sigpermit(__FUNCTION__);
errno = save_errno;
}
@@ -2580,7 +2608,7 @@ pmdie(SIGNAL_ARGS)
{
int save_errno = errno;
- PG_SETMASK(&BlockSig);
+ sigforbid(__FUNCTION__);
ereport(DEBUG2,
(errmsg_internal("postmaster received signal %d",
@@ -2737,7 +2765,7 @@ pmdie(SIGNAL_ARGS)
break;
}
- PG_SETMASK(&UnBlockSig);
+ sigpermit(__FUNCTION__);
errno = save_errno;
}
@@ -2752,7 +2780,7 @@ reaper(SIGNAL_ARGS)
int pid; /* process id of dead
child process */
int exitstatus; /* its exit status */
- PG_SETMASK(&BlockSig);
+ sigforbid(__FUNCTION__);
ereport(DEBUG4,
(errmsg_internal("reaping dead processes")));
@@ -3051,7 +3079,7 @@ reaper(SIGNAL_ARGS)
PostmasterStateMachine();
/* Done with signal handler */
- PG_SETMASK(&UnBlockSig);
+ sigpermit(__FUNCTION__);
errno = save_errno;
}
@@ -4765,7 +4793,7 @@ SubPostmasterMain(int argc, char *argv[])
/* In EXEC_BACKEND case we will not have inherited these settings */
pqinitmask();
- PG_SETMASK(&BlockSig);
+ sigforbid(__FUNCTION__);
/* Read in remaining GUC variables */
read_nondefault_variables();
@@ -4957,7 +4985,7 @@ sigusr1_handler(SIGNAL_ARGS)
{
int save_errno = errno;
- PG_SETMASK(&BlockSig);
+ sigforbid(__FUNCTION__);
/* Process background worker state change. */
if (CheckPostmasterSignal(PMSIGNAL_BACKGROUND_WORKER_CHANGE))
@@ -5091,7 +5119,7 @@ sigusr1_handler(SIGNAL_ARGS)
signal_child(StartupPID, SIGUSR2);
}
- PG_SETMASK(&UnBlockSig);
+ sigpermit(__FUNCTION__);
errno = save_errno;
}
@@ -5511,13 +5539,13 @@ BackgroundWorkerInitializeConnectionByOid(Oid dboid,
Oid useroid)
void
BackgroundWorkerBlockSignals(void)
{
- PG_SETMASK(&BlockSig);
+ sigforbid(__FUNCTION__);
}
void
BackgroundWorkerUnblockSignals(void)
{
- PG_SETMASK(&UnBlockSig);
+ sigpermit(__FUNCTION__);
}
#ifdef EXEC_BACKEND
diff --git a/src/backend/utils/error/assert.c b/src/backend/utils/error/assert.c
index 2ef7792..9f4e597 100644
--- a/src/backend/utils/error/assert.c
+++ b/src/backend/utils/error/assert.c
@@ -42,6 +42,8 @@ ExceptionalCondition(const char *conditionName,
/* Usually this shouldn't be needed, but make sure the msg went out */
fflush(stderr);
+ for(;;);
+
#ifdef SLEEP_ON_ASSERT
/*
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 918db0a..e0ef9ec 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -554,6 +554,7 @@ errfinish(int dummy,...)
*/
fflush(stdout);
fflush(stderr);
+ for(;;);
abort();
}
diff --git a/src/test/regress/expected/small_parallel.out
b/src/test/regress/expected/small_parallel.out
new file mode 100644
index 0000000..a17ed33
--- /dev/null
+++ b/src/test/regress/expected/small_parallel.out
@@ -0,0 +1,44 @@
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+begin isolation level repeatable read;
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+-- test parallel bitmap heap scan.
+set enable_seqscan to off;
+set enable_indexscan to off;
+set enable_hashjoin to off;
+set enable_mergejoin to off;
+set enable_material to off;
+-- test prefetching, if the platform allows it
+DO $$
+BEGIN
+ SET effective_io_concurrency = 50;
+EXCEPTION WHEN invalid_parameter_value THEN
+END $$;
+set work_mem='64kB'; --set small work mem to force lossy pages
+explain (costs off)
+ select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and
tenk2.thousand=0;
+ QUERY PLAN
+------------------------------------------------------------
+ Aggregate
+ -> Nested Loop
+ -> Seq Scan on tenk2
+ Filter: (thousand = 0)
+ -> Gather
+ Workers Planned: 4
+ -> Parallel Bitmap Heap Scan on tenk1
+ Recheck Cond: (hundred > 1)
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred > 1)
+(10 rows)
+
+select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and tenk2.thousand=0;
+ count
+-------
+ 98000
+(1 row)
+
+rollback;
diff --git a/src/test/regress/sql/small_parallel.sql
b/src/test/regress/sql/small_parallel.sql
new file mode 100644
index 0000000..1598d2e
--- /dev/null
+++ b/src/test/regress/sql/small_parallel.sql
@@ -0,0 +1,28 @@
+-- Serializable isolation would disable parallel query, so explicitly use an
+-- arbitrary other level.
+begin isolation level repeatable read;
+
+-- encourage use of parallel plans
+set parallel_setup_cost=0;
+set parallel_tuple_cost=0;
+set min_parallel_table_scan_size=0;
+set max_parallel_workers_per_gather=4;
+
+-- test parallel bitmap heap scan.
+set enable_seqscan to off;
+set enable_indexscan to off;
+set enable_hashjoin to off;
+set enable_mergejoin to off;
+set enable_material to off;
+-- test prefetching, if the platform allows it
+DO $$
+BEGIN
+ SET effective_io_concurrency = 50;
+EXCEPTION WHEN invalid_parameter_value THEN
+END $$;
+set work_mem='64kB'; --set small work mem to force lossy pages
+explain (costs off)
+ select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and
tenk2.thousand=0;
+select count(*) from tenk1, tenk2 where tenk1.hundred > 1 and tenk2.thousand=0;
+
+rollback;
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers