diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c index 19a1398d8a..fc73030214 100644 --- a/src/backend/postmaster/bgworker.c +++ b/src/backend/postmaster/bgworker.c @@ -12,13 +12,24 @@ #include "postgres.h" +#include +#include + #include "miscadmin.h" +#include "libpq/pqsignal.h" #include "postmaster/bgworker_internals.h" #include "storage/barrier.h" +#include "storage/ipc.h" +#include "storage/latch.h" #include "storage/lwlock.h" #include "storage/pmsignal.h" +#include "storage/proc.h" +#include "storage/procsignal.h" #include "storage/shmem.h" +#include "tcop/tcopprot.h" #include "utils/ascii.h" +#include "utils/ps_status.h" +#include "utils/timeout.h" /* * The postmaster's list of registered background workers, in private memory. @@ -354,6 +365,214 @@ SanityCheckBackgroundWorker(BackgroundWorker *worker, int elevel) return true; } +static void +bgworker_quickdie(SIGNAL_ARGS) +{ + sigaddset(&BlockSig, SIGQUIT); /* prevent nested calls */ + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(0) here, not exit(2) like quickdie. The reason is that + * we don't want to be seen this worker as independently crashed, because + * then postmaster would delay restarting it again afterwards. If some + * idiot DBA manually sends SIGQUIT to a random bgworker, the "dead man + * switch" will ensure that postmaster sees this as a crash. + */ + exit(0); +} + +/* + * Standard SIGTERM handler for background workers + */ +static void +bgworker_die(SIGNAL_ARGS) +{ + PG_SETMASK(&BlockSig); + + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), + errmsg("terminating background worker \"%s\" due to administrator command", + MyBgworkerEntry->bgw_name))); +} + +/* + * Standard SIGUSR1 handler for unconnected workers + * + * Here, we want to make sure an unconnected worker will at least heed + * latch activity. + */ +static void +bgworker_sigusr1_handler(SIGNAL_ARGS) +{ + int save_errno = errno; + + latch_sigusr1_handler(); + + errno = save_errno; +} + +/* + * Start a new background worker + * + * This is the main entry point for background worker, to be called from + * postmaster. + */ +void +StartBackgroundWorker(void) +{ + sigjmp_buf local_sigjmp_buf; + char buf[MAXPGPATH]; + BackgroundWorker *worker = MyBgworkerEntry; + bgworker_main_type entrypt; + + if (worker == NULL) + elog(FATAL, "unable to find bgworker entry"); + + /* we are a postmaster subprocess now */ + IsUnderPostmaster = true; + IsBackgroundWorker = true; + + /* reset MyProcPid */ + MyProcPid = getpid(); + + /* record Start Time for logging */ + MyStartTime = time(NULL); + + /* Identify myself via ps */ + snprintf(buf, MAXPGPATH, "bgworker: %s", worker->bgw_name); + init_ps_display(buf, "", "", ""); + + SetProcessingMode(InitProcessing); + + /* Apply PostAuthDelay */ + if (PostAuthDelay > 0) + pg_usleep(PostAuthDelay * 1000000L); + + /* + * If possible, make this process a group leader, so that the postmaster + * can signal any child processes too. + */ +#ifdef HAVE_SETSID + if (setsid() < 0) + elog(FATAL, "setsid() failed: %m"); +#endif + + /* + * Set up signal handlers. + */ + if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION) + { + /* + * SIGINT is used to signal canceling the current action + */ + pqsignal(SIGINT, StatementCancelHandler); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGFPE, FloatExceptionHandler); + + /* XXX Any other handlers needed here? */ + } + else + { + pqsignal(SIGINT, SIG_IGN); + pqsignal(SIGUSR1, bgworker_sigusr1_handler); + pqsignal(SIGFPE, SIG_IGN); + } + pqsignal(SIGTERM, bgworker_die); + pqsignal(SIGHUP, SIG_IGN); + + pqsignal(SIGQUIT, bgworker_quickdie); + InitializeTimeouts(); /* establishes SIGALRM handler */ + + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR2, SIG_IGN); + pqsignal(SIGCHLD, SIG_DFL); + + /* + * If an exception is encountered, processing resumes here. + * + * See notes in postgres.c about the design of this coding. + */ + if (sigsetjmp(local_sigjmp_buf, 1) != 0) + { + /* Since not using PG_TRY, must reset error stack by hand */ + error_context_stack = NULL; + + /* Prevent interrupts while cleaning up */ + HOLD_INTERRUPTS(); + + /* Report the error to the server log */ + EmitErrorReport(); + + /* + * Do we need more cleanup here? For shmem-connected bgworkers, we + * will call InitProcess below, which will install ProcKill as exit + * callback. That will take care of releasing locks, etc. + */ + + /* and go away */ + proc_exit(1); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* Early initialization */ + BaseInit(); + + /* + * If necessary, create a per-backend PGPROC struct in shared memory, + * except in the EXEC_BACKEND case where this was done in + * SubPostmasterMain. We must do this before we can use LWLocks (and in + * the EXEC_BACKEND case we already had to do some stuff with LWLocks). + */ +#ifndef EXEC_BACKEND + if (worker->bgw_flags & BGWORKER_SHMEM_ACCESS) + InitProcess(); +#endif + + /* + * If bgw_main is set, we use that value as the initial entrypoint. + * However, if the library containing the entrypoint wasn't loaded at + * postmaster startup time, passing it as a direct function pointer is + * not possible. To work around that, we allow callers for whom a + * function pointer is not available to pass a library name (which will + * be loaded, if necessary) and a function name (which will be looked up + * in the named library). + */ + if (worker->bgw_main != NULL) + entrypt = worker->bgw_main; + else + entrypt = (bgworker_main_type) + load_external_function(worker->bgw_library_name, + worker->bgw_function_name, + true, NULL); + + /* + * Note that in normal processes, we would call InitPostgres here. For a + * worker, however, we don't know what database to connect to, yet; so we + * need to wait until the user code does it via + * BackgroundWorkerInitializeConnection(). + */ + + /* + * Now invoke the user-defined worker code + */ + entrypt(worker->bgw_main_arg); + + /* ... and if it returns, we're done */ + proc_exit(0); +} + /* * Register a new background worker while processing shared_preload_libraries. * diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 841b5ec61d..13f4147d77 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -383,7 +383,6 @@ static void dummy_handler(SIGNAL_ARGS); static void StartupPacketTimeoutHandler(void); static void CleanupBackend(int pid, int exitstatus); static bool CleanupBackgroundWorker(int pid, int exitstatus); -static void do_start_bgworker(void); static void HandleChildCrash(int pid, int exitstatus, const char *procname); static void LogChildExit(int lev, const char *procname, int pid, int exitstatus); @@ -409,7 +408,7 @@ static void TerminateChildren(int signal); static int CountChildren(int target); static int CountUnconnectedWorkers(void); -static void StartOneBackgroundWorker(void); +static void maybe_start_bgworker(void); static bool CreateOptsFile(int argc, char *argv[], char *fullprogname); static pid_t StartChildProcess(AuxProcType type); static void StartAutovacuumWorker(void); @@ -1232,7 +1231,7 @@ PostmasterMain(int argc, char *argv[]) pmState = PM_STARTUP; /* Some workers may be scheduled to start now */ - StartOneBackgroundWorker(); + maybe_start_bgworker(); status = ServerLoop(); @@ -1650,7 +1649,7 @@ ServerLoop(void) /* Get other worker processes running, if needed */ if (StartWorkerNeeded || HaveCrashedWorker) - StartOneBackgroundWorker(); + maybe_start_bgworker(); /* * Touch Unix socket and lock files every 58 minutes, to ensure that @@ -2610,7 +2609,7 @@ reaper(SIGNAL_ARGS) PgStatPID = pgstat_start(); /* some workers may be scheduled to start now */ - StartOneBackgroundWorker(); + maybe_start_bgworker(); /* at this point we are really open for business */ ereport(LOG, @@ -4626,7 +4625,7 @@ SubPostmasterMain(int argc, char *argv[]) shmem_slot = atoi(argv[1] + 15); MyBgworkerEntry = BackgroundWorkerEntry(shmem_slot); - do_start_bgworker(); + StartBackgroundWorker(); } if (strcmp(argv[1], "--forkarch") == 0) { @@ -4741,7 +4740,7 @@ sigusr1_handler(SIGNAL_ARGS) } if (start_bgworker) - StartOneBackgroundWorker(); + maybe_start_bgworker(); if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER) && PgArchPID != 0) @@ -5253,208 +5252,6 @@ BackgroundWorkerUnblockSignals(void) PG_SETMASK(&UnBlockSig); } -static void -bgworker_quickdie(SIGNAL_ARGS) -{ - sigaddset(&BlockSig, SIGQUIT); /* prevent nested calls */ - PG_SETMASK(&BlockSig); - - /* - * We DO NOT want to run proc_exit() callbacks -- we're here because - * shared memory may be corrupted, so we don't want to try to clean up our - * transaction. Just nail the windows shut and get out of town. Now that - * there's an atexit callback to prevent third-party code from breaking - * things by calling exit() directly, we have to reset the callbacks - * explicitly to make this work as intended. - */ - on_exit_reset(); - - /* - * Note we do exit(0) here, not exit(2) like quickdie. The reason is that - * we don't want to be seen this worker as independently crashed, because - * then postmaster would delay restarting it again afterwards. If some - * idiot DBA manually sends SIGQUIT to a random bgworker, the "dead man - * switch" will ensure that postmaster sees this as a crash. - */ - exit(0); -} - -/* - * Standard SIGTERM handler for background workers - */ -static void -bgworker_die(SIGNAL_ARGS) -{ - PG_SETMASK(&BlockSig); - - ereport(FATAL, - (errcode(ERRCODE_ADMIN_SHUTDOWN), - errmsg("terminating background worker \"%s\" due to administrator command", - MyBgworkerEntry->bgw_name))); -} - -/* - * Standard SIGUSR1 handler for unconnected workers - * - * Here, we want to make sure an unconnected worker will at least heed - * latch activity. - */ -static void -bgworker_sigusr1_handler(SIGNAL_ARGS) -{ - int save_errno = errno; - - latch_sigusr1_handler(); - - errno = save_errno; -} - -static void -do_start_bgworker(void) -{ - sigjmp_buf local_sigjmp_buf; - char buf[MAXPGPATH]; - BackgroundWorker *worker = MyBgworkerEntry; - bgworker_main_type entrypt; - - if (worker == NULL) - elog(FATAL, "unable to find bgworker entry"); - - /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; - IsBackgroundWorker = true; - - /* reset MyProcPid */ - MyProcPid = getpid(); - - /* record Start Time for logging */ - MyStartTime = time(NULL); - - /* Identify myself via ps */ - snprintf(buf, MAXPGPATH, "bgworker: %s", worker->bgw_name); - init_ps_display(buf, "", "", ""); - - SetProcessingMode(InitProcessing); - - /* Apply PostAuthDelay */ - if (PostAuthDelay > 0) - pg_usleep(PostAuthDelay * 1000000L); - - /* - * If possible, make this process a group leader, so that the postmaster - * can signal any child processes too. - */ -#ifdef HAVE_SETSID - if (setsid() < 0) - elog(FATAL, "setsid() failed: %m"); -#endif - - /* - * Set up signal handlers. - */ - if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION) - { - /* - * SIGINT is used to signal canceling the current action - */ - pqsignal(SIGINT, StatementCancelHandler); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); - pqsignal(SIGFPE, FloatExceptionHandler); - - /* XXX Any other handlers needed here? */ - } - else - { - pqsignal(SIGINT, SIG_IGN); - pqsignal(SIGUSR1, bgworker_sigusr1_handler); - pqsignal(SIGFPE, SIG_IGN); - } - pqsignal(SIGTERM, bgworker_die); - pqsignal(SIGHUP, SIG_IGN); - - pqsignal(SIGQUIT, bgworker_quickdie); - InitializeTimeouts(); /* establishes SIGALRM handler */ - - pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR2, SIG_IGN); - pqsignal(SIGCHLD, SIG_DFL); - - /* - * If an exception is encountered, processing resumes here. - * - * See notes in postgres.c about the design of this coding. - */ - if (sigsetjmp(local_sigjmp_buf, 1) != 0) - { - /* Since not using PG_TRY, must reset error stack by hand */ - error_context_stack = NULL; - - /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); - - /* Report the error to the server log */ - EmitErrorReport(); - - /* - * Do we need more cleanup here? For shmem-connected bgworkers, we - * will call InitProcess below, which will install ProcKill as exit - * callback. That will take care of releasing locks, etc. - */ - - /* and go away */ - proc_exit(1); - } - - /* We can now handle ereport(ERROR) */ - PG_exception_stack = &local_sigjmp_buf; - - /* Early initialization */ - BaseInit(); - - /* - * If necessary, create a per-backend PGPROC struct in shared memory, - * except in the EXEC_BACKEND case where this was done in - * SubPostmasterMain. We must do this before we can use LWLocks (and in - * the EXEC_BACKEND case we already had to do some stuff with LWLocks). - */ -#ifndef EXEC_BACKEND - if (worker->bgw_flags & BGWORKER_SHMEM_ACCESS) - InitProcess(); -#endif - - /* - * If bgw_main is set, we use that value as the initial entrypoint. - * However, if the library containing the entrypoint wasn't loaded at - * postmaster startup time, passing it as a direct function pointer is - * not possible. To work around that, we allow callers for whom a - * function pointer is not available to pass a library name (which will - * be loaded, if necessary) and a function name (which will be looked up - * in the named library). - */ - if (worker->bgw_main != NULL) - entrypt = worker->bgw_main; - else - entrypt = (bgworker_main_type) - load_external_function(worker->bgw_library_name, - worker->bgw_function_name, - true, NULL); - - /* - * Note that in normal processes, we would call InitPostgres here. For a - * worker, however, we don't know what database to connect to, yet; so we - * need to wait until the user code does it via - * BackgroundWorkerInitializeConnection(). - */ - - /* - * Now invoke the user-defined worker code - */ - entrypt(worker->bgw_main_arg); - - /* ... and if it returns, we're done */ - proc_exit(0); -} - #ifdef EXEC_BACKEND static pid_t bgworker_forkexec(int shmem_slot) @@ -5483,7 +5280,7 @@ bgworker_forkexec(int shmem_slot) * This code is heavily based on autovacuum.c, q.v. */ static void -start_bgworker(RegisteredBgWorker *rw) +do_start_bgworker(RegisteredBgWorker *rw) { pid_t worker_pid; @@ -5514,7 +5311,7 @@ start_bgworker(RegisteredBgWorker *rw) /* Do NOT release postmaster's working memory context */ MyBgworkerEntry = &rw->rw_worker; - do_start_bgworker(); + StartBackgroundWorker(); break; #endif default: @@ -5618,7 +5415,7 @@ assign_backendlist_entry(RegisteredBgWorker *rw) * system state requires it. */ static void -StartOneBackgroundWorker(void) +maybe_start_bgworker(void) { slist_mutable_iter iter; TimestampTz now = 0; @@ -5689,7 +5486,7 @@ StartOneBackgroundWorker(void) else rw->rw_child_slot = MyPMChildSlot = AssignPostmasterChildSlot(); - start_bgworker(rw); /* sets rw->rw_pid */ + do_start_bgworker(rw); /* sets rw->rw_pid */ if (rw->rw_backend) { diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h index 0c4c8c325d..3b53027fd4 100644 --- a/src/include/postmaster/bgworker_internals.h +++ b/src/include/postmaster/bgworker_internals.h @@ -41,6 +41,9 @@ extern void BackgroundWorkerShmemInit(void); extern void BackgroundWorkerStateChange(void); extern void ForgetBackgroundWorker(slist_mutable_iter *cur); +/* Function to start a background worker, called from postmaster.c */ +extern void StartBackgroundWorker(void); + #ifdef EXEC_BACKEND extern BackgroundWorker *BackgroundWorkerEntry(int slotno); #endif