Sending SIGABRT to child processes (was Re: Strange failure on mamba) - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Sending SIGABRT to child processes (was Re: Strange failure on mamba) |
Date | |
Msg-id | 2251016.1668797294@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Strange failure on mamba (Andres Freund <andres@anarazel.de>) |
Responses |
Re: Sending SIGABRT to child processes (was Re: Strange failure on mamba)
|
List | pgsql-hackers |
Andres Freund <andres@anarazel.de> writes: > On 2022-11-17 17:47:50 -0500, Tom Lane wrote: >> So I'd like to have some way to make the postmaster send SIGABRT instead >> of SIGKILL in the buildfarm environment. The lowest-tech way would be >> to drive that off some #define or other. We could scale it up to a GUC >> perhaps. Adjacent to that, I also wonder whether SIGABRT wouldn't be >> more useful than SIGSTOP for the existing SendStop half-a-feature --- >> the idea that people should collect cores manually seems mighty >> last-century. > I suspect that having a GUC would be a good idea. I needed something similar > recently, debugging an occasional hang in the AIO patchset. I first tried > something like your #define approach and it did cause a problematic flood of > core files. Yeah, the main downside of such a thing is the risk of lots of core files accumulating over repeated crashes. Nonetheless, I think it'll be a useful debugging aid. Here's a proposed patch. (I took the opportunity to kill off the long-since-unimplemented Reinit switch, too.) One thing I'm not too clear on is if we want to send SIGABRT to the child groups (ie, SIGABRT grandchild processes too). I made signal_child do so here, but perhaps it's overkill. regards, tom lane diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index bd50ea8e48..24b1624bad 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -11500,6 +11500,62 @@ LOG: CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1) </listitem> </varlistentry> + <varlistentry id="guc-send-abort-for-crash" xreflabel="send_abort_for_crash"> + <term><varname>send_abort_for_crash</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>send_abort_for_crash</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + By default, after a backend crash the postmaster will stop remaining + child processes by sending them <systemitem>SIGQUIT</systemitem> + signals, which permits them to exit more-or-less gracefully. When + this option is set to <literal>on</literal>, + <systemitem>SIGABRT</systemitem> is sent instead. That normally + results in production of a core dump file for each such child + process. + This can be handy for investigating the states of other processes + after a crash. It can also consume lots of disk space in the event + of repeated crashes, so do not enable this on systems you are not + monitoring carefully. + Beware that no support exists for cleaning up the core file(s) + automatically. + This parameter can only be set in + the <filename>postgresql.conf</filename> file or on the server + command line. + </para> + </listitem> + </varlistentry> + + <varlistentry id="guc-send-abort-for-kill" xreflabel="send_abort_for_kill"> + <term><varname>send_abort_for_kill</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>send_abort_for_kill</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + By default, after attempting to stop a child process with + <systemitem>SIGQUIT</systemitem>, the postmaster will wait five + seconds and then send <systemitem>SIGKILL</systemitem> to force + immediate termination. When this option is set + to <literal>on</literal>, <systemitem>SIGABRT</systemitem> is sent + instead of <systemitem>SIGKILL</systemitem>. That normally results + in production of a core dump file for each such child process. + This can be handy for investigating the states + of <quote>stuck</quote> child processes. It can also consume lots + of disk space in the event of repeated crashes, so do not enable + this on systems you are not monitoring carefully. + Beware that no support exists for cleaning up the core file(s) + automatically. + This parameter can only be set in + the <filename>postgresql.conf</filename> file or on the server + command line. + </para> + </listitem> + </varlistentry> + </variablelist> </sect1> <sect1 id="runtime-config-short"> diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml index 55a3f6c69d..b13a16a117 100644 --- a/doc/src/sgml/ref/postgres-ref.sgml +++ b/doc/src/sgml/ref/postgres-ref.sgml @@ -409,24 +409,6 @@ PostgreSQL documentation </listitem> </varlistentry> - <varlistentry> - <term><option>-n</option></term> - <listitem> - <para> - This option is for debugging problems that cause a server - process to die abnormally. The ordinary strategy in this - situation is to notify all other server processes that they - must terminate and then reinitialize the shared memory and - semaphores. This is because an errant server process could - have corrupted some shared state before terminating. This - option specifies that <command>postgres</command> will - not reinitialize shared data structures. A knowledgeable - system programmer can then use a debugger to examine shared - memory and semaphore state. - </para> - </listitem> - </varlistentry> - <varlistentry> <term><option>-O</option></term> <listitem> @@ -466,14 +448,9 @@ PostgreSQL documentation This option is for debugging problems that cause a server process to die abnormally. The ordinary strategy in this situation is to notify all other server processes that they - must terminate and then reinitialize the shared memory and - semaphores. This is because an errant server process could - have corrupted some shared state before terminating. This - option specifies that <command>postgres</command> will - stop all other server processes by sending the signal - <literal>SIGSTOP</literal>, but will not cause them to - terminate. This permits system programmers to collect core - dumps from all server processes by hand. + must terminate, by sending them <systemitem>SIGQUIT</systemitem> + signals. With this option, <systemitem>SIGABRT</systemitem> + will be sent instead, resulting in production of core dump files. </para> </listitem> </varlistentry> diff --git a/src/backend/main/main.c b/src/backend/main/main.c index f5da4260a1..67f556b4dd 100644 --- a/src/backend/main/main.c +++ b/src/backend/main/main.c @@ -352,11 +352,10 @@ help(const char *progname) printf(_("\nDeveloper options:\n")); printf(_(" -f s|i|o|b|t|n|m|h forbid use of some plan types\n")); - printf(_(" -n do not reinitialize shared memory after abnormal exit\n")); printf(_(" -O allow system table structure changes\n")); printf(_(" -P disable system indexes\n")); printf(_(" -t pa|pl|ex show timings after each query\n")); - printf(_(" -T send SIGSTOP to all backend processes if one dies\n")); + printf(_(" -T send SIGABRT to all backend processes if one dies\n")); printf(_(" -W NUM wait NUM seconds to allow attach from a debugger\n")); printf(_("\nOptions for single-user mode:\n")); diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 1da5752047..c83cc8cc6c 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -219,16 +219,6 @@ int ReservedBackends; #define MAXLISTEN 64 static pgsocket ListenSocket[MAXLISTEN]; -/* - * These globals control the behavior of the postmaster in case some - * backend dumps core. Normally, it kills all peers of the dead backend - * and reinitializes shared memory. By specifying -s or -n, we can have - * the postmaster stop (rather than kill) peers and not reinitialize - * shared data structures. (Reinit is currently dead code, though.) - */ -static bool Reinit = true; -static int SendStop = false; - /* still more option variables */ bool EnableSSL = false; @@ -243,6 +233,8 @@ bool enable_bonjour = false; char *bonjour_name; bool restart_after_crash = true; bool remove_temp_files_after_crash = true; +bool send_abort_for_crash = false; +bool send_abort_for_kill = false; /* PIDs of special child processes; 0 when not running */ static pid_t StartupPID = 0, @@ -414,6 +406,7 @@ static void report_fork_failure_to_client(Port *port, int errnum); static CAC_state canAcceptConnections(int backend_type); static bool RandomCancelKey(int32 *cancel_key); static void signal_child(pid_t pid, int signal); +static void sigquit_child(pid_t pid); static bool SignalSomeChildren(int signal, int target); static void TerminateChildren(int signal); @@ -697,7 +690,7 @@ PostmasterMain(int argc, char *argv[]) * tcop/postgres.c (the option sets should not conflict) and with the * common help() function in main/main.c. */ - while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:W:-:")) != -1) + while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:OPp:r:S:sTt:W:-:")) != -1) { switch (opt) { @@ -767,11 +760,6 @@ PostmasterMain(int argc, char *argv[]) SetConfigOption("max_connections", optarg, PGC_POSTMASTER, PGC_S_ARGV); break; - case 'n': - /* Don't reinit shared mem after abnormal exit */ - Reinit = false; - break; - case 'O': SetConfigOption("allow_system_table_mods", "true", PGC_POSTMASTER, PGC_S_ARGV); break; @@ -799,11 +787,10 @@ PostmasterMain(int argc, char *argv[]) case 'T': /* - * In the event that some backend dumps core, send SIGSTOP, - * rather than SIGQUIT, to all its peers. This lets the wily - * post_hacker collect core dumps from everyone. + * This option used to be defined as sending SIGSTOP after a + * backend crash, but sending SIGABRT seems more useful. */ - SendStop = true; + SetConfigOption("send_abort_for_crash", "true", PGC_POSTMASTER, PGC_S_ARGV); break; case 't': @@ -1896,20 +1883,23 @@ ServerLoop(void) /* * If we already sent SIGQUIT to children and they are slow to shut - * down, it's time to send them SIGKILL. This doesn't happen - * normally, but under certain conditions backends can get stuck while - * shutting down. This is a last measure to get them unwedged. + * down, it's time to send them SIGKILL (or SIGABRT if requested). + * This doesn't happen normally, but under certain conditions backends + * can get stuck while shutting down. This is a last measure to get + * them unwedged. * * Note we also do this during recovery from a process crash. */ - if ((Shutdown >= ImmediateShutdown || (FatalError && !SendStop)) && + if ((Shutdown >= ImmediateShutdown || FatalError) && AbortStartTime != 0 && (now - AbortStartTime) >= SIGKILL_CHILDREN_AFTER_SECS) { /* We were gentle with them before. Not anymore */ ereport(LOG, - (errmsg("issuing SIGKILL to recalcitrant children"))); - TerminateChildren(SIGKILL); + /* translator: %s is SIGKILL or SIGABRT */ + (errmsg("issuing %s to recalcitrant children", + send_abort_for_kill ? "SIGABRT" : "SIGKILL"))); + TerminateChildren(send_abort_for_kill ? SIGABRT : SIGKILL); /* reset flag so we don't SIGKILL again */ AbortStartTime = 0; } @@ -2903,6 +2893,7 @@ pmdie(SIGNAL_ARGS) #endif /* tell children to shut down ASAP */ + /* (note we don't apply send_abort_for_crash here) */ SetQuitSignalReason(PMQUIT_FOR_STOP); TerminateChildren(SIGQUIT); pmState = PM_WAIT_BACKENDS; @@ -3470,20 +3461,9 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) /* * This worker is still alive. Unless we did so already, tell it * to commit hara-kiri. - * - * SIGQUIT is the special signal that says exit without proc_exit - * and let the user know what's going on. But if SendStop is set - * (-T on command line), then we send SIGSTOP instead, so that we - * can get core dumps from all backends by hand. */ if (take_action) - { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) rw->rw_pid))); - signal_child(rw->rw_pid, (SendStop ? SIGSTOP : SIGQUIT)); - } + sigquit_child(rw->rw_pid); } } @@ -3514,13 +3494,8 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) * This backend is still alive. Unless we did so already, tell it * to commit hara-kiri. * - * SIGQUIT is the special signal that says exit without proc_exit - * and let the user know what's going on. But if SendStop is set - * (-T on command line), then we send SIGSTOP instead, so that we - * can get core dumps from all backends by hand. - * - * We could exclude dead_end children here, but at least in the - * SIGSTOP case it seems better to include them. + * We could exclude dead_end children here, but at least when + * sending SIGABRT it seems better to include them. * * Background workers were already processed above; ignore them * here. @@ -3529,13 +3504,7 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) continue; if (take_action) - { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) bp->pid))); - signal_child(bp->pid, (SendStop ? SIGSTOP : SIGQUIT)); - } + sigquit_child(bp->pid); } } @@ -3547,11 +3516,7 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) } else if (StartupPID != 0 && take_action) { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) StartupPID))); - signal_child(StartupPID, (SendStop ? SIGSTOP : SIGQUIT)); + sigquit_child(StartupPID); StartupStatus = STARTUP_SIGNALED; } @@ -3559,73 +3524,37 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) if (pid == BgWriterPID) BgWriterPID = 0; else if (BgWriterPID != 0 && take_action) - { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) BgWriterPID))); - signal_child(BgWriterPID, (SendStop ? SIGSTOP : SIGQUIT)); - } + sigquit_child(BgWriterPID); /* Take care of the checkpointer too */ if (pid == CheckpointerPID) CheckpointerPID = 0; else if (CheckpointerPID != 0 && take_action) - { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) CheckpointerPID))); - signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); - } + sigquit_child(CheckpointerPID); /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; else if (WalWriterPID != 0 && take_action) - { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) WalWriterPID))); - signal_child(WalWriterPID, (SendStop ? SIGSTOP : SIGQUIT)); - } + sigquit_child(WalWriterPID); /* Take care of the walreceiver too */ if (pid == WalReceiverPID) WalReceiverPID = 0; else if (WalReceiverPID != 0 && take_action) - { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) WalReceiverPID))); - signal_child(WalReceiverPID, (SendStop ? SIGSTOP : SIGQUIT)); - } + sigquit_child(WalReceiverPID); /* Take care of the autovacuum launcher too */ if (pid == AutoVacPID) AutoVacPID = 0; else if (AutoVacPID != 0 && take_action) - { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) AutoVacPID))); - signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); - } + sigquit_child(AutoVacPID); /* Take care of the archiver too */ if (pid == PgArchPID) PgArchPID = 0; else if (PgArchPID != 0 && take_action) - { - ereport(DEBUG2, - (errmsg_internal("sending %s to process %d", - (SendStop ? "SIGSTOP" : "SIGQUIT"), - (int) PgArchPID))); - signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); - } + sigquit_child(PgArchPID); /* We do NOT restart the syslogger */ @@ -3834,6 +3763,10 @@ PostmasterStateMachine(void) * Any required cleanup will happen at next restart. We * set FatalError so that an "abnormal shutdown" message * gets logged when we exit. + * + * We don't consult send_abort_for_crash here, as it's + * unlikely that dumping cores would illuminate the reason + * for checkpointer fork failure. */ FatalError = true; pmState = PM_WAIT_DEAD_END; @@ -4003,8 +3936,8 @@ signal_child(pid_t pid, int signal) case SIGINT: case SIGTERM: case SIGQUIT: - case SIGSTOP: case SIGKILL: + case SIGABRT: if (kill(-pid, signal) < 0) elog(DEBUG3, "kill(%ld,%d) failed: %m", (long) (-pid), signal); break; @@ -4014,6 +3947,24 @@ signal_child(pid_t pid, int signal) #endif } +/* + * Convenience function for killing a child process after a crash of some + * other child process. We log the action at a higher level than we would + * otherwise do, and we apply send_abort_for_crash to decide which signal + * to send. Normally it's SIGQUIT -- and most other comments in this file + * are written on the assumption that it is -- but developers might prefer + * to use SIGABRT to collect per-child core dumps. + */ +static void +sigquit_child(pid_t pid) +{ + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (send_abort_for_crash ? "SIGABRT" : "SIGQUIT"), + (int) pid))); + signal_child(pid, (send_abort_for_crash ? SIGABRT : SIGQUIT)); +} + /* * Send a signal to the targeted children (but NOT special children; * dead_end children are never signaled, either). @@ -4069,7 +4020,7 @@ TerminateChildren(int signal) if (StartupPID != 0) { signal_child(StartupPID, signal); - if (signal == SIGQUIT || signal == SIGKILL) + if (signal == SIGQUIT || signal == SIGKILL || signal == SIGABRT) StartupStatus = STARTUP_SIGNALED; } if (BgWriterPID != 0) diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 836b49484a..349dd6a537 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -1227,6 +1227,26 @@ struct config_bool ConfigureNamesBool[] = true, NULL, NULL, NULL }, + { + {"send_abort_for_crash", PGC_SIGHUP, DEVELOPER_OPTIONS, + gettext_noop("Send SIGABRT not SIGQUIT to child processes after backend crash."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &send_abort_for_crash, + false, + NULL, NULL, NULL + }, + { + {"send_abort_for_kill", PGC_SIGHUP, DEVELOPER_OPTIONS, + gettext_noop("Send SIGABRT not SIGKILL to stuck child processes."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &send_abort_for_kill, + false, + NULL, NULL, NULL + }, { {"log_duration", PGC_SUSET, LOGGING_WHAT, diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index 90e333ccd2..f078321df2 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -30,6 +30,8 @@ extern PGDLLIMPORT bool enable_bonjour; extern PGDLLIMPORT char *bonjour_name; extern PGDLLIMPORT bool restart_after_crash; extern PGDLLIMPORT bool remove_temp_files_after_crash; +extern PGDLLIMPORT bool send_abort_for_crash; +extern PGDLLIMPORT bool send_abort_for_kill; #ifdef WIN32 extern PGDLLIMPORT HANDLE PostmasterHandle;
pgsql-hackers by date: