diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml index 9e6dea98a24..6996d731868 100644 --- a/doc/src/sgml/bgworker.sgml +++ b/doc/src/sgml/bgworker.sgml @@ -184,13 +184,18 @@ typedef struct BackgroundWorker argument to RegisterDynamicBackgroundWorker. If the worker is successfully registered, this pointer will be initialized with an opaque handle that can subsequently be passed to - GetBackgroundWorkerPid(BackgroundWorkerHandle *, pid_t *). - This function can be used to poll the status of the worker: a return - value of BGWH_NOT_YET_STARTED indicates that the worker has not - yet been started by the postmaster; BGWH_STOPPED - indicates that it has been started but is no longer running; and - BGWH_STARTED indicates that it is currently running. - In this last case, the PID will also be returned via the second argument. + GetBackgroundWorkerPid(BackgroundWorkerHandle *, pid_t *) or + TerminateBackgroundWorker(BackgroundWorkerHandle *). + GetBackgroundWorker can be used to poll the status of the + worker: a return value of BGWH_NOT_YET_STARTED indicates that + the worker has not yet been started by the postmaster; + BGWH_STOPPED indicates that it has been started but is + no longer running; and BGWH_STARTED indicates that it is + currently running. In this last case, the PID will also be returned via the + second argument. + TerminateBackgroundWorker causes the postmaster to send + SIGTERM to the worker if it is running, and to unregister it + as soon as it is not. diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c index 0e7a4a53a14..39c81b3aab0 100644 --- a/src/backend/postmaster/bgworker.c +++ b/src/backend/postmaster/bgworker.c @@ -55,6 +55,11 @@ slist_head BackgroundWorkerList = SLIST_STATIC_INIT(BackgroundWorkerList); * must fully initialize the slot - and insert a write memory barrier - before * marking it as in use. * + * As an exception, however, even when the slot is in use, regular backends + * may set the 'terminate' flag for a slot, telling the postmaster not + * to restart it. Once the background worker is no longer running, the slot + * will be released for reuse. + * * In addition to coordinating with the postmaster, backends modifying this * data structure must coordinate with each other. Since they can take locks, * this is straightforward: any backend wishing to manipulate a slot must @@ -67,6 +72,7 @@ slist_head BackgroundWorkerList = SLIST_STATIC_INIT(BackgroundWorkerList); typedef struct BackgroundWorkerSlot { bool in_use; + bool terminate; pid_t pid; /* InvalidPid = not started yet; 0 = dead */ uint64 generation; /* incremented when slot is recycled */ BackgroundWorker worker; @@ -134,6 +140,7 @@ BackgroundWorkerShmemInit(void) rw = slist_container(RegisteredBgWorker, rw_lnode, siter.cur); Assert(slotno < max_worker_processes); slot->in_use = true; + slot->terminate = false; slot->pid = InvalidPid; slot->generation = 0; rw->rw_shmem_slot = slotno; @@ -223,14 +230,29 @@ BackgroundWorkerStateChange(void) */ pg_read_barrier(); - /* - * See whether we already know about this worker. If not, we need - * to update our backend-private BackgroundWorkerList to match shared - * memory. - */ + /* See whether we already know about this worker. */ rw = FindRegisteredWorkerBySlotNumber(slotno); if (rw != NULL) + { + /* + * In general, the worker data can't change after it's initially + * registered. However, someone can set the terminate flag. + */ + if (slot->terminate && !rw->rw_terminate) + { + rw->rw_terminate = true; + if (rw->rw_pid != 0) + kill(rw->rw_pid, SIGTERM); + } continue; + } + + /* If it's already flagged as do not restart, just release the slot. */ + if (slot->terminate) + { + slot->in_use = false; + continue; + } /* * Copy the registration data into the registered workers list. @@ -292,6 +314,7 @@ BackgroundWorkerStateChange(void) rw->rw_child_slot = 0; rw->rw_crashed_at = 0; rw->rw_shmem_slot = slotno; + rw->rw_terminate = false; /* Log it! */ ereport(LOG, @@ -714,6 +737,7 @@ RegisterBackgroundWorker(BackgroundWorker *worker) rw->rw_pid = 0; rw->rw_child_slot = 0; rw->rw_crashed_at = 0; + rw->rw_terminate = false; slist_push_head(&BackgroundWorkerList, &rw->rw_lnode); } @@ -764,6 +788,7 @@ RegisterDynamicBackgroundWorker(BackgroundWorker *worker, memcpy(&slot->worker, worker, sizeof(BackgroundWorker)); slot->pid = InvalidPid; /* indicates not started yet */ slot->generation++; + slot->terminate = false; generation = slot->generation; /* @@ -905,3 +930,33 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp) set_latch_on_sigusr1 = save_set_latch_on_sigusr1; return status; } + +/* + * Instruct the postmaster to terminate a background worker. + * + * Note that it's safe to do this without regard to whether the worker is + * still running, or even if the worker may already have existed and been + * unregistered. + */ +void +TerminateBackgroundWorker(BackgroundWorkerHandle *handle) +{ + BackgroundWorkerSlot *slot; + bool signal_postmaster = false; + + Assert(handle->slot < max_worker_processes); + slot = &BackgroundWorkerData->slot[handle->slot]; + + /* Set terminate flag in shared memory, unless slot has been reused. */ + LWLockAcquire(BackgroundWorkerLock, LW_EXCLUSIVE); + if (handle->generation == slot->generation) + { + slot->terminate = true; + signal_postmaster = true; + } + LWLockRelease(BackgroundWorkerLock); + + /* Make sure the postmaster notices the change to shared memory. */ + if (signal_postmaster) + SendPostmasterSignal(PMSIGNAL_BACKGROUND_WORKER_CHANGE); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 98086f78410..351887b1333 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1463,7 +1463,8 @@ DetermineSleepTime(struct timeval * timeout) if (rw->rw_crashed_at == 0) continue; - if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART) + if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART + || rw->rw_terminate) { ForgetBackgroundWorker(&siter); continue; @@ -5471,6 +5472,13 @@ maybe_start_bgworker(void) if (rw->rw_pid != 0) continue; + /* marked for death? */ + if (rw->rw_terminate) + { + ForgetBackgroundWorker(&iter); + continue; + } + /* * If this worker has crashed previously, maybe it needs to be * restarted (unless on registration it specified it doesn't want to diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h index 8bbbeb492c9..c27b08bf1ef 100644 --- a/src/include/postmaster/bgworker.h +++ b/src/include/postmaster/bgworker.h @@ -9,17 +9,22 @@ * worker. Workers can also be registered dynamically at runtime. In either * case, the worker process is forked from the postmaster and runs the * user-supplied "main" function. This code may connect to a database and - * run transactions. Once started, it stays active until shutdown or crash; - * unless the restart interval is declared as BGW_NEVER_RESTART and the - * process exits with a return code of 1; workers that do this are - * automatically unregistered by the postmaster. + * run transactions. Workers can remain active indefinitely, but will be + * terminated if a shutdown or crash occurs. * * If the fork() call fails in the postmaster, it will try again later. Note * that the failure can only be transient (fork failure due to high load, * memory pressure, too many processes, etc); more permanent problems, like * failure to connect to a database, are detected later in the worker and dealt - * with just by having the worker exit normally. Postmaster will launch a new - * worker again later. + * with just by having the worker exit normally. A worker which exits with a + * return code of 0 will be immediately restarted by the postmaster. A worker + * which exits with a return code of 1 will be restarted after the configured + * restart interval, or never if that interval is set to BGW_NEVER_RESTART. + * The TerminateBackgroundWorker() function can be used to terminate a + * dynamically registered background worker; the worker will be sent a SIGTERM + * and will not be restarted after it exits. Whenever the postmaster knows + * that a worker will not be restarted, it unregisters the worker, freeing up + * that worker's slot for use by a new worker. * * Note that there might be more than one worker in a database concurrently, * and the same module may request more than one worker running the same (or @@ -107,6 +112,9 @@ extern BgwHandleStatus GetBackgroundWorkerPid(BackgroundWorkerHandle *handle, extern BgwHandleStatus WaitForBackgroundWorkerStartup(BackgroundWorkerHandle * handle, pid_t *pid); +/* Terminate a bgworker */ +extern void TerminateBackgroundWorker(BackgroundWorkerHandle *handle); + /* This is valid in a running worker */ extern BackgroundWorker *MyBgworkerEntry; diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h index 6d5ee20dc30..e7e1ab94ade 100644 --- a/src/include/postmaster/bgworker_internals.h +++ b/src/include/postmaster/bgworker_internals.h @@ -31,6 +31,7 @@ typedef struct RegisteredBgWorker int rw_child_slot; TimestampTz rw_crashed_at; /* if not 0, time it last crashed */ int rw_shmem_slot; + bool rw_terminate; slist_node rw_lnode; /* list link */ } RegisteredBgWorker;