diff --git a/contrib/auth_counter/Makefile b/contrib/auth_counter/Makefile new file mode 100644 index 0000000..74ac012 --- /dev/null +++ b/contrib/auth_counter/Makefile @@ -0,0 +1,14 @@ +# contrib/auth_counter/Makefile + +MODULES = auth_counter + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/auth_counter +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/auth_counter/auth_counter.c b/contrib/auth_counter/auth_counter.c new file mode 100644 index 0000000..20f946a --- /dev/null +++ b/contrib/auth_counter/auth_counter.c @@ -0,0 +1,203 @@ +/* ------------------------------------------------------------------------- + * + * auth_counter.c + * + * Copyright (C) 2012, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/auth_delay/auth_counter.c* + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/auth.h" +#include "libpq/pqsignal.h" +#include "miscadmin.h" +#include "postmaster/bgworker.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" +#include "storage/smgr.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/resowner.h" +#include "utils/timestamp.h" +#include + +PG_MODULE_MAGIC; + +void _PG_init(void); + +/* GUC variable */ +static int auth_counter_interval; + +/* Original hooks */ +static ClientAuthentication_hook_type original_client_auth_hook; +static shmem_startup_hook_type shmem_startup_hook_next; + +/* Counter value and flag of loop */ +static LWLockId auth_counter_lock; +static long *auth_counter_success; +static long *auth_counter_failed; +static bool auth_counter_loop; + +/* + * auth_counter_sigterm + * + * It reset auth_counter_loop to terminate current loop. + */ +static void +auth_counter_sigterm(SIGNAL_ARGS) +{ + auth_counter_loop = false; +} + +/* + * auth_counter_main + * + * The main routine of this extra daemon; that logs number of successful + * and failed authentication for each intervals unless receiving a signal. + */ +static int +auth_counter_main(const char *daemon_name) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext auth_counter_context; + + /* Loop condition should be set */ + auth_counter_loop = true; + + /* Create a resource owner to keep track of our resources */ + CurrentResourceOwner = ResourceOwnerCreate(NULL, "Auth-Counter"); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. Formerly this code just ran in + * TopMemoryContext, but resetting that would be a really bad idea. + */ + auth_counter_context = AllocSetContextCreate(TopMemoryContext, + "Auth Counter", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + MemoryContextSwitchTo(auth_counter_context); + + /* Unblock signals (they were blocked when the postmaster forked us) */ + PG_SETMASK(&UnBlockSig); + + /* + * Init counter variables + */ + LWLockAcquire(auth_counter_lock, LW_EXCLUSIVE); + *auth_counter_success = 0; + *auth_counter_failed = 0; + LWLockRelease(auth_counter_lock); + + while (auth_counter_loop) + { + Datum tstamp; + long n_success; + long n_failed; + + pg_usleep((long)auth_counter_interval * 1000000L); + + LWLockAcquire(auth_counter_lock, LW_EXCLUSIVE); + n_success = *auth_counter_success; + n_failed = *auth_counter_failed; + + *auth_counter_success = 0; + *auth_counter_failed = 0; + LWLockRelease(auth_counter_lock); + + tstamp = DirectFunctionCall1(timestamptz_out, + TimestampTzGetDatum(GetCurrentTimestamp())); + + elog(LOG, "%s(%d) %lu of login successful, %lu of failed - %s", + daemon_name, MyProcPid, n_success, n_failed, + DatumGetCString(tstamp)); + + /* clear temporary memory objects */ + MemoryContextReset(auth_counter_context); + } + return 0; +} + + +/* + * auth_counter_check + * + * It increments the counter variable for each client authentication + */ +static void +auth_counter_check(Port *port, int status) +{ + if (original_client_auth_hook) + original_client_auth_hook(port, status); + + LWLockAcquire(auth_counter_lock, LW_EXCLUSIVE); + if (status == STATUS_OK) + (*auth_counter_success)++; + else + (*auth_counter_failed)++; + LWLockRelease(auth_counter_lock); +} + +/* + * Callback just after shared memory allocation + */ +static void +auth_counter_shmem_startup(void) +{ + if (shmem_startup_hook_next) + shmem_startup_hook_next(); + + /* allocate lwlock to protect counters */ + auth_counter_lock = LWLockAssign(); + + /* allocate counter variable on shmem segment */ + auth_counter_success = ShmemAlloc(sizeof(long)); + auth_counter_failed = ShmemAlloc(sizeof(long)); +} + +/* + * Entrypoint of this module + */ +void +_PG_init(void) +{ + BackgroundWorker worker; + + DefineCustomIntVariable("auth_counter.interval", + "Interval to display number of logins", + NULL, + &auth_counter_interval, + 10, /* 1 minute (default) */ + 5, /* 5 seconds */ + 24 * 60 * 60, /* 1 day*/ + PGC_SIGHUP, + GUC_UNIT_S, + NULL, NULL, NULL); + + /* request for shared memory fraction */ + RequestAddinShmemSpace(2 * sizeof(long)); + + shmem_startup_hook_next = shmem_startup_hook; + shmem_startup_hook = auth_counter_shmem_startup; + + /* install a hook */ + original_client_auth_hook = ClientAuthentication_hook; + ClientAuthentication_hook = auth_counter_check; + + /* register the worker process */ + worker.bgw_name = "auth counter"; + worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; + worker.bgw_start_time = BgWorkerStart_RecoveryFinished; + worker.bgw_main = auth_counter_main; + worker.bgw_main_arg = NULL; + worker.bgw_sighup = auth_counter_sigterm; + worker.bgw_sigterm = auth_counter_sigterm; + + RegisterBackgroundWorker(&worker); +} diff --git a/contrib/worker_spi/Makefile b/contrib/worker_spi/Makefile new file mode 100644 index 0000000..edf4105 --- /dev/null +++ b/contrib/worker_spi/Makefile @@ -0,0 +1,14 @@ +# contrib/worker_spi/Makefile + +MODULES = worker_spi + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/worker_spi +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/worker_spi/worker_spi.c b/contrib/worker_spi/worker_spi.c new file mode 100644 index 0000000..93f7c7f --- /dev/null +++ b/contrib/worker_spi/worker_spi.c @@ -0,0 +1,177 @@ +/* ------------------------------------------------------------------------- + * + * worker_spi.c + * + * Copyright (C) 2012, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/worker_spi/worker_spi.c + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "access/xact.h" +#include "executor/spi.h" +#include "fmgr.h" +#include "lib/stringinfo.h" +#include "libpq/pqsignal.h" +#include "postmaster/bgworker.h" +#include "storage/ipc.h" +#include "utils/snapmgr.h" + +PG_MODULE_MAGIC; + +void _PG_init(void); + +static bool got_sigterm = false; + +static void +worker_spi_sigterm(SIGNAL_ARGS) +{ + got_sigterm = true; +} + +static void +worker_spi_sighup(SIGNAL_ARGS) +{ + elog(LOG, "got sighup!"); +} + +static void +initialize_worker_spi(char *tabname) +{ + int ret; + int ntup; + bool isnull; + StringInfoData buf; + + StartTransactionCommand(); + SPI_connect(); + PushActiveSnapshot(GetTransactionSnapshot()); + + initStringInfo(&buf); + appendStringInfo(&buf, "select count(*) from pg_namespace where nspname = '%s'", tabname); + + ret = SPI_execute(buf.data, true, 0); + if (ret != SPI_OK_SELECT) + elog(FATAL, "SPI_execute failed: error code %d", ret); + + if (SPI_processed != 1) + elog(FATAL, "not a singleton result"); + + ntup = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[0], + SPI_tuptable->tupdesc, + 1, &isnull)); + if (isnull) + elog(FATAL, "null result"); + + elog(LOG, "pg_namespace has %d tuples for nspname = '%s'", ntup, tabname); + + if (ntup == 0) + { + resetStringInfo(&buf); + appendStringInfo(&buf, + "create schema \"%s\" " + "create table \"%s\" (type text, " + "value int)", tabname, tabname); + + ret = SPI_execute(buf.data, false, 0); + + if (ret != SPI_OK_UTILITY) + elog(FATAL, "failed to create my schema"); + } + + SPI_finish(); + PopActiveSnapshot(); + CommitTransactionCommand(); +} + +static void +worker_spi_main(void *main_arg) +{ + char *tabname; + StringInfoData buf; + + tabname = (char *) main_arg; + + /* Unblock signals (they were blocked when the postmaster forked us) */ + PG_SETMASK(&UnBlockSig); + + BackgroundWorkerInitializeConnection("alvherre", NULL); + + initialize_worker_spi(tabname); + + initStringInfo(&buf); + appendStringInfo(&buf, + "WITH deleted AS (DELETE " + "FROM %s.%s " + "WHERE type = 'delta' RETURNING value), " + "total AS (SELECT coalesce(sum(value), 0) as sum " + "FROM deleted) " + "UPDATE %s.%s " + "SET value = %s.value + total.sum " + "FROM total WHERE type = 'total' " + "RETURNING %s.value", tabname, tabname, tabname, tabname, tabname, tabname); + + while (!got_sigterm) + { + int ret; + + pg_usleep(1000 * 1000 * 10); /* 10s */ + + StartTransactionCommand(); + SPI_connect(); + PushActiveSnapshot(GetTransactionSnapshot()); + + ret = SPI_execute(buf.data, false, 0); + + if (ret != SPI_OK_UPDATE_RETURNING) + elog(FATAL, "cannot select from table %s: error code %d", tabname, ret); + + if (SPI_processed > 0) + { + bool isnull; + int32 val; + + val = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[0], + SPI_tuptable->tupdesc, + 1, &isnull)); + if (!isnull) + elog(LOG, "count is now %d", val); + } + + SPI_finish(); + PopActiveSnapshot(); + CommitTransactionCommand(); + } + + proc_exit(0); +} + +/* + * Entrypoint of this module + */ +void +_PG_init(void) +{ + BackgroundWorker worker; + + /* register the worker process */ + worker.bgw_name = "SPI worker"; + worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; + worker.bgw_start_time = BgWorkerStart_RecoveryFinished; + worker.bgw_main = worker_spi_main; + worker.bgw_sighup = worker_spi_sighup; + worker.bgw_sigterm = worker_spi_sigterm; + + worker.bgw_name = "SPI worker 1"; + worker.bgw_main_arg = "table1"; + RegisterBackgroundWorker(&worker); + + worker.bgw_name = "SPI worker 2"; + worker.bgw_main_arg = "table2"; + RegisterBackgroundWorker(&worker); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index dff4c71..f386f78 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -103,6 +103,7 @@ #include "miscadmin.h" #include "pgstat.h" #include "postmaster/autovacuum.h" +#include "postmaster/bgworker.h" #include "postmaster/fork_process.h" #include "postmaster/pgarch.h" #include "postmaster/postmaster.h" @@ -132,7 +133,8 @@ * children we have and send them appropriate signals when necessary. * * "Special" children such as the startup, bgwriter and autovacuum launcher - * tasks are not in this list. Autovacuum worker and walsender processes are + * tasks are not in this list. Autovacuum worker, walsender and general + * background worker processes are * in it. Also, "dead_end" children are in it: these are children launched just * for the purpose of sending a friendly rejection message to a would-be * client. We must track them because they are attached to shared memory, @@ -144,7 +146,12 @@ typedef struct bkend pid_t pid; /* process id of backend */ long cancel_key; /* cancel key for cancels for this backend */ int child_slot; /* PMChildSlot for this backend, if any */ - bool is_autovacuum; /* is it an autovacuum process? */ + int bkend_type; /* flavor of backend or auxiliary process + Note that BACKEND_TYPE_WALSND backends + initially announce themselves as + BACKEND_TYPE_NORMAL, so if bkend_type is + normal then you should check for a recent + transition. */ bool dead_end; /* is it going to send an error and quit? */ Dlelem elem; /* list link in BackendList */ } Backend; @@ -155,6 +162,23 @@ static Dllist *BackendList; static Backend *ShmemBackendArray; #endif + +/* + * List of background workers. + */ +typedef struct RegisteredBgWorker +{ + BackgroundWorker *worker; /* its registry entry */ + Backend *backend; /* its BackendList entry, or NULL */ + pid_t pid; /* 0 if not running */ + int child_slot; + TimestampTz crashed_at; /* if not 0, time it last crashed */ +} RegisteredBgWorker; + +static List *BackgroundWorkerList = NIL; + + + /* The socket number we are listening for connections on */ int PostPortNumber; /* The directory names for Unix socket(s) */ @@ -306,6 +330,10 @@ static volatile sig_atomic_t start_autovac_launcher = false; /* the launcher needs to be signalled to communicate some condition */ static volatile bool avlauncher_needs_signal = false; +/* set when there's a worker that needs to be started up */ +static volatile bool StartWorkerNeeded = true; +static volatile bool HaveCrashedWorker = false; + /* * State for assigning random salts and cancel keys. * Also, the global MyCancelKey passes the cancel key assigned to a given @@ -343,6 +371,8 @@ static void startup_die(SIGNAL_ARGS); 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_worker(BackgroundWorker *worker); static void HandleChildCrash(int pid, int exitstatus, const char *procname); static void LogChildExit(int lev, const char *procname, int pid, int exitstatus); @@ -361,6 +391,7 @@ static long PostmasterRandom(void); static void RandomSalt(char *md5Salt); static void signal_child(pid_t pid, int signal); static bool SignalSomeChildren(int signal, int targets); +static bool SignalUnconnectedWorkers(int signal); #define SignalChildren(sig) SignalSomeChildren(sig, BACKEND_TYPE_ALL) @@ -371,9 +402,14 @@ static bool SignalSomeChildren(int signal, int targets); #define BACKEND_TYPE_NORMAL 0x0001 /* normal backend */ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ -#define BACKEND_TYPE_ALL 0x0007 /* OR of all the above */ +#define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ +#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ + +#define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) static int CountChildren(int target); +static int CountUnconnectedWorkers(void); +static void StartOneBackgroundWorker(void); static bool CreateOptsFile(int argc, char *argv[], char *fullprogname); static pid_t StartChildProcess(AuxProcType type); static void StartAutovacuumWorker(void); @@ -1092,7 +1128,8 @@ PostmasterMain(int argc, char *argv[]) * handling setup of child processes. See tcop/postgres.c, * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/walwriter.c, * postmaster/autovacuum.c, postmaster/pgarch.c, postmaster/pgstat.c, - * postmaster/syslogger.c and postmaster/checkpointer.c. + * postmaster/syslogger.c, postmaster/bgworker.c and + * postmaster/checkpointer.c. */ pqinitmask(); PG_SETMASK(&BlockSig); @@ -1182,6 +1219,9 @@ PostmasterMain(int argc, char *argv[]) Assert(StartupPID != 0); pmState = PM_STARTUP; + /* Some workers may be scheduled to start now */ + StartOneBackgroundWorker(); + status = ServerLoop(); /* @@ -1369,9 +1409,6 @@ ServerLoop(void) /* * Wait for a connection request to arrive. * - * We wait at most one minute, to ensure that the other background - * tasks handled below get done even when no requests are arriving. - * * If we are in PM_WAIT_DEAD_END state, then we don't want to accept * any new connections, so we don't call select() at all; just sleep * for a little bit with signals unblocked. @@ -1390,8 +1427,23 @@ ServerLoop(void) /* must set timeout each time; some OSes change it! */ struct timeval timeout; - timeout.tv_sec = 60; - timeout.tv_usec = 0; + /* + * In normal conditions we wait at most one minute, to ensure that + * the other background tasks handled below get done even when no + * requests are arriving. However, if there are background workers + * waiting to be started, we don't actually sleep so that they are + * quickly serviced. + */ + if (StartWorkerNeeded) + { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + } + else + { + timeout.tv_sec = 60; + timeout.tv_usec = 0; + } selres = select(nSockets, &rmask, NULL, NULL, &timeout); } @@ -1503,6 +1555,10 @@ ServerLoop(void) kill(AutoVacPID, SIGUSR2); } + /* Get other worker processes running, if needed */ + if (StartWorkerNeeded || HaveCrashedWorker) + StartOneBackgroundWorker(); + /* * Touch Unix socket and lock files every 58 minutes, to ensure that * they are not removed by overzealous /tmp-cleaning tasks. We assume @@ -2210,8 +2266,11 @@ pmdie(SIGNAL_ARGS) if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { - /* autovacuum workers are told to shut down immediately */ - SignalSomeChildren(SIGTERM, BACKEND_TYPE_AUTOVAC); + /* autovac workers are told to shut down immediately */ + /* and bgworkers too; does this need tweaking? */ + SignalSomeChildren(SIGTERM, + BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER); + SignalUnconnectedWorkers(SIGTERM); /* and the autovac launcher too */ if (AutoVacPID != 0) signal_child(AutoVacPID, SIGTERM); @@ -2263,12 +2322,14 @@ pmdie(SIGNAL_ARGS) signal_child(BgWriterPID, SIGTERM); if (WalReceiverPID != 0) signal_child(WalReceiverPID, SIGTERM); + SignalUnconnectedWorkers(SIGTERM); if (pmState == PM_RECOVERY) { /* - * Only startup, bgwriter, and checkpointer should be active - * in this state; we just signaled the first two, and we don't - * want to kill checkpointer yet. + * Only startup, bgwriter, walreceiver, unconnected bgworkers, + * and checkpointer should be active in this state; we just + * signaled the first four, and we don't want to kill + * checkpointer yet. */ pmState = PM_WAIT_BACKENDS; } @@ -2280,9 +2341,10 @@ pmdie(SIGNAL_ARGS) { ereport(LOG, (errmsg("aborting any active transactions"))); - /* shut down all backends and autovac workers */ + /* shut down all backends and workers */ SignalSomeChildren(SIGTERM, - BACKEND_TYPE_NORMAL | BACKEND_TYPE_AUTOVAC); + BACKEND_TYPE_NORMAL | BACKEND_TYPE_AUTOVAC | + BACKEND_TYPE_BGWORKER); /* and the autovac launcher too */ if (AutoVacPID != 0) signal_child(AutoVacPID, SIGTERM); @@ -2326,6 +2388,7 @@ pmdie(SIGNAL_ARGS) signal_child(PgArchPID, SIGQUIT); if (PgStatPID != 0) signal_child(PgStatPID, SIGQUIT); + SignalUnconnectedWorkers(SIGQUIT); ExitPostmaster(0); break; } @@ -2454,6 +2517,9 @@ reaper(SIGNAL_ARGS) if (PgStatPID == 0) PgStatPID = pgstat_start(); + /* some workers may be scheduled to start now */ + StartOneBackgroundWorker(); + /* at this point we are really open for business */ ereport(LOG, (errmsg("database system is ready to accept connections"))); @@ -2620,6 +2686,14 @@ reaper(SIGNAL_ARGS) continue; } + /* Was it one of our background workers? */ + if (CleanupBackgroundWorker(pid, exitstatus)) + { + /* have it be restarted */ + StartWorkerNeeded = true; + continue; + } + /* * Else do standard backend child cleanup. */ @@ -2638,11 +2712,93 @@ reaper(SIGNAL_ARGS) errno = save_errno; } +/* + * Scan the bgworkers list and see if the given PID (which has just stopped + * or crashed) is in it. Handle its shutdown if so, and return true. If not a + * bgworker, return false. + * + * This is heavily based on CleanupBackend, q.v. + */ +static bool +CleanupBackgroundWorker(int pid, + int exitstatus) /* child's exit status */ +{ + ListCell *cell; + char namebuf[MAXPGPATH]; + + LogChildExit(DEBUG2, _("background worker process"), pid, exitstatus); + +#ifdef WIN32 + if (exitstatus == ERROR_WAIT_NO_CHILDREN) + { + LogChildExit(LOG, _("background server process"), pid, exitstatus); + exitstatus = 0; + } +#endif + + foreach(cell, BackgroundWorkerList) + { + RegisteredBgWorker *rw = lfirst(cell); + + if (rw->pid != pid) + continue; + + snprintf(namebuf, MAXPGPATH, "%s: %s", _("worker process"), + rw->worker->bgw_name); + + if (!EXIT_STATUS_0(exitstatus) && !EXIT_STATUS_1(exitstatus)) + { + if (rw->worker->bgw_flags & BGWORKER_SHMEM_ACCESS) + { + /* XXX do we need more cleanup here? */ + rw->crashed_at = GetCurrentTimestamp(); + HandleChildCrash(pid, exitstatus, namebuf); + return true; + } + } + + if (!ReleasePostmasterChildSlot(rw->child_slot)) + { + /* + * Uh-oh, the child failed to clean itself up. Treat as a + * crash after all. + */ + rw->crashed_at = GetCurrentTimestamp(); + HandleChildCrash(pid, exitstatus, namebuf); + return true; + } + + /* Get it out of the BackendList and clear out remaining data */ + if (rw->backend) + { + DLRemove(&rw->backend->elem); +#ifdef EXEC_BACKEND + ShmemBackendArrayRemove(rw->backend); +#endif + free(rw->backend); + rw->backend = NULL; + } + rw->pid = 0; + rw->child_slot = 0; + if (EXIT_STATUS_1(exitstatus)) + rw->crashed_at = GetCurrentTimestamp(); + else + rw->crashed_at = 0; + + LogChildExit(LOG, namebuf, pid, exitstatus); + + return true; + } + + return false; +} /* * CleanupBackend -- cleanup after terminated backend. * * Remove all local state associated with backend. + * + * If you change this, see also CleanupBackgroundWorker. */ static void CleanupBackend(int pid, @@ -2686,6 +2842,8 @@ CleanupBackend(int pid, if (bp->pid == pid) { + /* XXX do we need to check bkend_type here? */ + if (!bp->dead_end) { if (!ReleasePostmasterChildSlot(bp->child_slot)) @@ -2710,7 +2868,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter or autovacuum. + * walwriter, autovacuum, or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -2721,6 +2879,7 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) Dlelem *curr, *next; Backend *bp; + ListCell *cell; /* * Make log entry unless there was a previous crash (if so, nonzero exit @@ -2733,6 +2892,53 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) (errmsg("terminating any other active server processes"))); } + /* Process background workers */ + foreach(cell, BackgroundWorkerList) + { + RegisteredBgWorker *rw = lfirst(cell); + + if (rw->pid == pid) + { + /* + * Found entry for freshly-dead worker, so remove it. + */ + (void) ReleasePostmasterChildSlot(rw->child_slot); + if (rw->backend) + { + DLRemove(&rw->backend->elem); +#ifdef EXEC_BACKEND + ShmemBackendArrayRemove(rw->backend); +#endif + free(rw->backend); + rw->backend = NULL; + } + rw->pid = 0; + rw->child_slot = 0; + /* don't reset crashed_at */ + /* Keep looping so we can signal remaining workers */ + } + else + { + /* + * 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 + * (-s on command line), then we send SIGSTOP instead, so that we + * can get core dumps from all backends by hand. + */ + if (!FatalError) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) rw->pid))); + signal_child(rw->pid, (SendStop ? SIGSTOP : SIGQUIT)); + } + } + } + /* Process regular backends */ for (curr = DLGetHead(BackendList); curr; curr = next) { @@ -2882,6 +3088,24 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) allow_immediate_pgstat_restart(); } + /* + * Power-cycle all shmem-connected bgworkers too + */ + foreach(cell, BackgroundWorkerList) + { + RegisteredBgWorker *rw = lfirst(cell); + + if (rw->pid != 0 && + (rw->worker->bgw_flags & BGWORKER_SHMEM_ACCESS)) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + "SIGQUIT", + (int) rw->pid))); + signal_child(rw->pid, SIGQUIT); + } + } + /* We do NOT restart the syslogger */ FatalError = true; @@ -3011,7 +3235,8 @@ PostmasterStateMachine(void) { /* * PM_WAIT_BACKENDS state ends when we have no regular backends - * (including autovac workers) and no walwriter, autovac launcher or + * (including autovac workers), no bgworkers (including unconnected + * ones), and no walwriter, autovac launcher or * bgwriter. If we are doing crash recovery then we expect the * checkpointer to exit as well, otherwise not. The archiver, stats, * and syslogger processes are disregarded since they are not @@ -3020,7 +3245,8 @@ PostmasterStateMachine(void) * later after writing the checkpoint record, like the archiver * process. */ - if (CountChildren(BACKEND_TYPE_NORMAL | BACKEND_TYPE_AUTOVAC) == 0 && + if (CountChildren(BACKEND_TYPE_NORMAL | BACKEND_TYPE_WORKER) == 0 && + CountUnconnectedWorkers() == 0 && StartupPID == 0 && WalReceiverPID == 0 && BgWriterPID == 0 && @@ -3233,6 +3459,35 @@ signal_child(pid_t pid, int signal) } /* + * Send a signal to bgworkers that did not request backend connections + */ +static bool +SignalUnconnectedWorkers(int signal) +{ + ListCell *cell; + bool signaled = false; + + foreach(cell, BackgroundWorkerList) + { + RegisteredBgWorker *rw = lfirst(cell); + + if (rw->pid == 0) + continue; + /* ignore connected workers */ + if (rw->backend != NULL) + continue; + + ereport(DEBUG4, + (errmsg_internal("sending signal %d to process %d", + signal, (int) rw->pid))); + signal_child(rw->pid, signal); + signaled = true; + } + + return signaled; +} + +/* * Send a signal to the targeted children (but NOT special children; * dead_end children are never signaled, either). */ @@ -3255,15 +3510,15 @@ SignalSomeChildren(int signal, int target) */ if (target != BACKEND_TYPE_ALL) { - int child; + /* + * Assign bkend_type for any recently announced + * WAL Sender processes. + */ + if (bp->bkend_type == BACKEND_TYPE_NORMAL && + IsPostmasterChildWalSender(bp->child_slot)) + bp->bkend_type = BACKEND_TYPE_WALSND; - if (bp->is_autovacuum) - child = BACKEND_TYPE_AUTOVAC; - else if (IsPostmasterChildWalSender(bp->child_slot)) - child = BACKEND_TYPE_WALSND; - else - child = BACKEND_TYPE_NORMAL; - if (!(target & child)) + if (!(target & bp->bkend_type)) continue; } @@ -3381,7 +3636,7 @@ BackendStartup(Port *port) * of backends. */ bn->pid = pid; - bn->is_autovacuum = false; + bn->bkend_type = BACKEND_TYPE_NORMAL; /* Can change later to WALSND */ DLInitElem(&bn->elem, bn); DLAddHead(BackendList, &bn->elem); #ifdef EXEC_BACKEND @@ -3757,7 +4012,10 @@ internal_forkexec(int argc, char *argv[], Port *port) fp = AllocateFile(tmpfilename, PG_BINARY_W); if (!fp) { - /* As in OpenTemporaryFile, try to make the temp-file directory */ + /* + * As in OpenTemporaryFileInTablespace, try to make the temp-file + * directory + */ mkdir(PG_TEMP_FILES_DIR, S_IRWXU); fp = AllocateFile(tmpfilename, PG_BINARY_W); @@ -4325,6 +4583,9 @@ sigusr1_handler(SIGNAL_ARGS) (errmsg("database system is ready to accept read only connections"))); pmState = PM_HOT_STANDBY; + + /* Some workers may be scheduled to start now */ + StartOneBackgroundWorker(); } if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER) && @@ -4492,6 +4753,30 @@ PostmasterRandom(void) } /* + * Count up number of worker processes that did not request backend connections + */ +static int +CountUnconnectedWorkers(void) +{ + ListCell *cell; + int cnt = 0; + + foreach(cell, BackgroundWorkerList) + { + RegisteredBgWorker *rw = lfirst(cell); + + if (rw->pid == 0) + continue; + /* ignore connected workers */ + if (rw->backend != NULL) + continue; + + cnt++; + } + return cnt; +} + +/* * Count up number of child processes of specified types (dead_end chidren * are always excluded). */ @@ -4514,15 +4799,15 @@ CountChildren(int target) */ if (target != BACKEND_TYPE_ALL) { - int child; + /* + * Assign bkend_type for any recently announced + * WAL Sender processes. + */ + if (bp->bkend_type == BACKEND_TYPE_NORMAL && + IsPostmasterChildWalSender(bp->child_slot)) + bp->bkend_type = BACKEND_TYPE_WALSND; - if (bp->is_autovacuum) - child = BACKEND_TYPE_AUTOVAC; - else if (IsPostmasterChildWalSender(bp->child_slot)) - child = BACKEND_TYPE_WALSND; - else - child = BACKEND_TYPE_NORMAL; - if (!(target & child)) + if (!(target & bp->bkend_type)) continue; } @@ -4681,7 +4966,7 @@ StartAutovacuumWorker(void) bn->pid = StartAutoVacWorker(); if (bn->pid > 0) { - bn->is_autovacuum = true; + bn->bkend_type = BACKEND_TYPE_AUTOVAC; DLInitElem(&bn->elem, bn); DLAddHead(BackendList, &bn->elem); #ifdef EXEC_BACKEND @@ -4757,7 +5042,8 @@ CreateOptsFile(int argc, char *argv[], char *fullprogname) * * This reports the number of entries needed in per-child-process arrays * (the PMChildFlags array, and if EXEC_BACKEND the ShmemBackendArray). - * These arrays include regular backends, autovac workers and walsenders, + * These arrays include regular backends, autovac workers, walsenders + * and background workers, * but not special children nor dead_end children. This allows the arrays * to have a fixed maximum size, to wit the same too-many-children limit * enforced by canAcceptConnections(). The exact value isn't too critical @@ -4769,6 +5055,447 @@ MaxLivePostmasterChildren(void) return 2 * MaxBackends; } +/* + * Register a new background worker. + * + * This can only be called in the _PG_init function of a module library + * that's loaded by shared_preload_libraries; otherwise it has no effect. + */ +void +RegisterBackgroundWorker(BackgroundWorker *worker) +{ + RegisteredBgWorker *rw; + + if (!process_shared_preload_libraries_in_progress) + { + ereport(LOG, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("background worker must be registered in shared_preload_libraries"))); + return; + } + + /* sanity check for flags */ + if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION) + { + if (!(worker->bgw_flags & BGWORKER_SHMEM_ACCESS)) + { + ereport(LOG, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("background worker must attach to shared memory in order to request a database connection"))); + return; + } + + if (worker->bgw_start_time == BgWorkerStart_PostmasterStart) + { + ereport(LOG, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("background worker cannot request database access if starting at postmaster start"))); + return; + } + + /* XXX other checks? */ + } + + rw = malloc(MAXALIGN(sizeof(RegisteredBgWorker)) + + MAXALIGN(sizeof(BackgroundWorker)) + + strlen(worker->bgw_name) + 1); /* FIXME -- check for NULL and missing \0 terminator? */ + if (rw == NULL) + { + ereport(LOG, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + return; + } + + rw->worker = (BackgroundWorker *) + MAXALIGN((char *) rw + sizeof(RegisteredBgWorker)); + memcpy(rw->worker, worker, sizeof(BackgroundWorker)); + rw->worker->bgw_name = (char *) + MAXALIGN((char *) rw->worker + sizeof(BackgroundWorker)); + strlcpy(rw->worker->bgw_name, worker->bgw_name, strlen(worker->bgw_name) + 1); /* FIXME strlen again */ + + rw->backend = NULL; + rw->pid = 0; + rw->child_slot = 0; + rw->crashed_at = 0; + + /* XXX should we worry about the memcxt this happens in? */ + BackgroundWorkerList = lappend(BackgroundWorkerList, rw); +} + +/* + * Connect worker to a database. + * + * XXX is this really necessary, or would it be better to have module call + * InitPostgres() directly? If we were to have more stuff done here this would + * be more justified; as is, it doesn't seem to. + */ +void +BackgroundWorkerInitializeConnection(char *dbname, char *username) +{ + /* + * XXX Here we need to check that the BGWORKER_BACKEND_DATABASE_CONNECTION + * flag was passed on registration, or raise an error otherwise. Not sure + * how ... Maybe we need a "MyBgWorker" global pointer somehow (similar to + * MyProc) that InitPostgres can check by itself. That would require that + * the BackgroundWorker structs live in shared memory ... + */ + + InitPostgres(dbname, InvalidOid, username, NULL); + + /* it had better not gotten out of "init" mode yet */ + if (!IsInitProcessingMode()) + ereport(ERROR, + (errmsg("invalid processing mode in bgworker"))); + SetProcessingMode(NormalProcessing); +} + +static void +do_start_worker(BackgroundWorker *worker) +{ + sigjmp_buf local_sigjmp_buf; + char buf[MAXPGPATH]; + + /* 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); + + /* + * 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, SIG_IGN); + pqsignal(SIGFPE, SIG_IGN); /* XXX may need something else for FPE */ + } + + /* These are configurable */ + pqsignal(SIGTERM, worker->bgw_sigterm); + pqsignal(SIGHUP, worker->bgw_sighup); + + pqsignal(SIGQUIT, 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(); + + /* + * we might need more cleanup here ... LWLockReleaseAll, etc. + * Note that in some cases we will call InitPocess which will register + * ProcKill as exit callback. + */ + + /* 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 + + /* + * 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 + */ + worker->bgw_main(worker->bgw_main_arg); + + /* ... and if it returns, we're done */ + proc_exit(0); +} + +int +GetNumRegisteredBackgroundWorkers(void) +{ + return list_length(BackgroundWorkerList); +} + +/* + * Start a new bgworker. + * Starting time conditions must have been checked already. + * + * This code is heavily based on autovacuum.c, q.v. + */ +static void +start_bgworker(RegisteredBgWorker *rw) +{ + pid_t worker_pid; + + ereport(LOG, + (errmsg("starting background worker process \"%s\"", rw->worker->bgw_name))); + +#ifdef EXEC_BACKEND + switch ((worker_pid = bgworker_forkexec())) +#else + switch ((worker_pid = fork_process())) +#endif + { + case -1: + ereport(LOG, + (errmsg("could not fork worker process: %m"))); + return; + +#ifndef EXEC_BACKEND + case 0: + /* in postmaster child ... */ + /* Close the postmaster's sockets */ + ClosePostmasterPorts(false); + + /* Lose the postmaster's on-exit routines */ + on_exit_reset(); + + /* Do NOT release postmaster's working memory context */ + + do_start_worker(rw->worker); + break; +#endif + default: + elog(LOG, "setting worker pid to %d", worker_pid); + rw->pid = worker_pid; + if (rw->backend) + rw->backend->pid = rw->pid; + } +} + +/* + * Does the current postmaster state require starting a worker with the + * specified start_time? + */ +static bool +bgworker_should_start_now(BgWorkerStartTime start_time) +{ + /* XXX maybe this'd be better as a table */ + switch (pmState) + { + case PM_NO_CHILDREN: + case PM_WAIT_DEAD_END: + case PM_SHUTDOWN_2: + case PM_SHUTDOWN: + case PM_WAIT_BACKENDS: + case PM_WAIT_READONLY: + case PM_WAIT_BACKUP: + break; + + case PM_RUN: + if (start_time == BgWorkerStart_RecoveryFinished) + return true; + /* fall through */ + + case PM_HOT_STANDBY: + if (start_time == BgWorkerStart_ConsistentState) + return true; + /* fall through */ + + case PM_RECOVERY: + case PM_STARTUP: + case PM_INIT: + if (start_time == BgWorkerStart_PostmasterStart) + return true; + /* fall through */ + } + + return false; +} + +/* + * Allocate the Backend struct for a connected background worker, and add it + * to the BackendList. + * + * Some info from the Backend is copied into the passed rw. + */ +static bool +assign_backendlist_entry(RegisteredBgWorker *rw) +{ + Backend *bn = malloc(sizeof(Backend)); + + if (bn == NULL) + { + ereport(LOG, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + /* + * The worker didn't really crash, but setting this nonzero makes + * postmaster wait a bit before attempting to start it again; if it + * tried again right away, most likely it'd find itself under the same + * memory pressure. + */ + rw->crashed_at = GetCurrentTimestamp(); + return false; + } + + /* + * Compute the cancel key that will be assigned to this session. + * We probably don't need cancel keys for background workers, but + * we'd better have something random in the field to prevent + * unfriendly people from sending cancels to them. + */ + MyCancelKey = PostmasterRandom(); + bn->cancel_key = MyCancelKey; + + bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot(); + bn->bkend_type = BACKEND_TYPE_BGWORKER; + bn->dead_end = false; + DLInitElem(&bn->elem, bn); + DLAddHead(BackendList, &bn->elem); +#ifdef EXEC_BACKEND + ShmemBackendArrayAdd(bn); +#endif + + rw->backend = bn; + rw->child_slot = bn->child_slot; + + return true; +} + +/* + * If the time is right, start one background worker. If there are more + * workers that need to be started, StartWorkerNeeded is set to true; + * otherwise it is reset. + */ +static void +StartOneBackgroundWorker(void) +{ + ListCell *cell; + TimestampTz now = 0; + + HaveCrashedWorker = false; + + foreach(cell, BackgroundWorkerList) + { + RegisteredBgWorker *rw = lfirst(cell); + + /* already running? */ + if (rw->pid != 0) + continue; + + /* + * If this worker has crashed previously, check how long ago did it + * last happen. If the last crash is too recent, don't start the + * worker right away; but let it be restarted once enough time has + * passed. + * + * (The other alternative would be to have the worker not be started + * again at all until postmaster is restarted, but this doesn't seem + * as useful.) + */ + if (rw->crashed_at != 0) + { + if (now == 0) + now = GetCurrentTimestamp(); + + if (!TimestampDifferenceExceeds(rw->crashed_at, now, + 5 * 1000)) /* 60 seconds */ + { + HaveCrashedWorker = true; + continue; + } + } + + if (bgworker_should_start_now(rw->worker->bgw_start_time)) + { + /* reset crash time before calling assign_backendlist_entry */ + rw->crashed_at = 0; + + /* + * If necessary, allocate and assign the Backend element. Note we + * must do this before forking, so that we can handle out of memory + * properly. + * + * If not connected, we don't need a Backend element, but we still + * need a PMChildSlot. + */ + if (rw->worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION) + { + if (!assign_backendlist_entry(rw)) + return; + } + else + rw->child_slot = MyPMChildSlot = AssignPostmasterChildSlot(); + + start_bgworker(rw); /* sets rw->pid */ + + /* + * Have ServerLoop call us again. Note that there might not + * actually *be* another runnable worker, but we don't care all + * that much; we will find out the next time we run. + */ + StartWorkerNeeded = true; + return; + } + } + + /* no runnable worker found */ + StartWorkerNeeded = false; +} #ifdef EXEC_BACKEND diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 4b66bd3..8dd2b4b 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -87,6 +87,7 @@ pid_t PostmasterPid = 0; bool IsPostmasterEnvironment = false; bool IsUnderPostmaster = false; bool IsBinaryUpgrade = false; +bool IsBackgroundWorker = false; bool ExitOnAnyError = false; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 5288aa7..c518c21 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -498,10 +498,10 @@ void InitializeSessionUserIdStandalone(void) { /* - * This function should only be called in single-user mode and in - * autovacuum workers. + * This function should only be called in single-user mode, in + * autovacuum workers, and in background workers. */ - AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess()); + AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsBackgroundWorker); /* call only once */ AssertState(!OidIsValid(AuthenticatedUserId)); diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 2eb456d..b87ec6c 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -627,6 +627,19 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username, errhint("You should immediately run CREATE USER \"%s\" SUPERUSER;.", username))); } + else if (IsBackgroundWorker) + { + if (username == NULL) + { + InitializeSessionUserIdStandalone(); + am_superuser = true; + } + else + { + InitializeSessionUserId(username); + am_superuser = superuser(); + } + } else { /* normal multiuser case */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 48652b2..dad259a 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -52,6 +52,7 @@ #include "parser/scansup.h" #include "pgstat.h" #include "postmaster/autovacuum.h" +#include "postmaster/bgworker.h" #include "postmaster/bgwriter.h" #include "postmaster/postmaster.h" #include "postmaster/syslogger.h" @@ -108,7 +109,8 @@ * removed, we still could not exceed INT_MAX/4 because some places compute * 4*MaxBackends without any overflow check. This is rechecked in * check_maxconnections, since MaxBackends is computed as MaxConnections - * plus autovacuum_max_workers plus one (for the autovacuum launcher). + * plus the number of bgworkers plus autovacuum_max_workers plus one (for the + * autovacuum launcher). */ #define MAX_BACKENDS 0x7fffff @@ -8611,7 +8613,8 @@ show_tcp_keepalives_count(void) static bool check_maxconnections(int *newval, void **extra, GucSource source) { - if (*newval + autovacuum_max_workers + 1 > MAX_BACKENDS) + if (*newval + GetNumRegisteredBackgroundWorkers() + + autovacuum_max_workers + 1 > MAX_BACKENDS) return false; return true; } @@ -8619,13 +8622,15 @@ check_maxconnections(int *newval, void **extra, GucSource source) static void assign_maxconnections(int newval, void *extra) { - MaxBackends = newval + autovacuum_max_workers + 1; + MaxBackends = newval + autovacuum_max_workers + 1 + + GetNumRegisteredBackgroundWorkers(); } static bool check_autovacuum_max_workers(int *newval, void **extra, GucSource source) { - if (MaxConnections + *newval + 1 > MAX_BACKENDS) + if (MaxConnections + *newval + 1 + GetNumRegisteredBackgroundWorkers() > + MAX_BACKENDS) return false; return true; } @@ -8633,7 +8638,8 @@ check_autovacuum_max_workers(int *newval, void **extra, GucSource source) static void assign_autovacuum_max_workers(int newval, void *extra) { - MaxBackends = MaxConnections + newval + 1; + MaxBackends = MaxConnections + newval + 1 + + GetNumRegisteredBackgroundWorkers(); } static bool diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 9f57989..c2bf2e4 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -131,6 +131,7 @@ do { \ extern pid_t PostmasterPid; extern bool IsPostmasterEnvironment; extern PGDLLIMPORT bool IsUnderPostmaster; +extern bool IsBackgroundWorker; extern bool IsBinaryUpgrade; extern bool ExitOnAnyError; diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h new file mode 100644 index 0000000..4a7e506 --- /dev/null +++ b/src/include/postmaster/bgworker.h @@ -0,0 +1,93 @@ +/*-------------------------------------------------------------------- + * bgworker.h + * POSTGRES pluggable background workers interface + * + * A background worker is a process able to run arbitrary, user-supplied code, + * including normal transactions. + * + * Any external module loaded via shared_preload_libraries can register a + * worker. Then, at the appropriate time, 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. The process should sleep during periods of + * inactivity. + * + * 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. + * + * 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 + * different) code. + * + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/include/postmaster/bgworker.h + *-------------------------------------------------------------------- + */ +#ifndef BGWORKER_H +#define BGWORKER_H + +/*--------------------------------------------------------------------- + * External module API. + *--------------------------------------------------------------------- + */ + +/* + * Pass this flag to have your worker be able to connect to shared memory. + */ +#define BGWORKER_SHMEM_ACCESS 0x0001 + +/* + * This flag means the bgworker requires a database connection. The connection + * is not established automatically; the worker must establish it later. + * It requires that BGWORKER_SHMEM_ACCESS was passed too. + */ +#define BGWORKER_BACKEND_DATABASE_CONNECTION 0x0002 + + +typedef void (*bgworker_main_type)(void *main_arg); +typedef void (*bgworker_sighdlr_type)(SIGNAL_ARGS); + +/* + * Points in time at which a bgworker can request to be started + */ +typedef enum +{ + BgWorkerStart_PostmasterStart, + BgWorkerStart_ConsistentState, + BgWorkerStart_RecoveryFinished +} BgWorkerStartTime; + +typedef struct BackgroundWorker +{ + char *bgw_name; + int bgw_flags; + BgWorkerStartTime bgw_start_time; + bgworker_main_type bgw_main; + void *bgw_main_arg; + bgworker_sighdlr_type bgw_sighup; + bgworker_sighdlr_type bgw_sigterm; +} BackgroundWorker; + +/* Register a new bgworker */ +extern void RegisterBackgroundWorker(BackgroundWorker *worker); + +/* + * Connect to the specified database, as the specified user. Only a worker + * that passed BGWORKER_BACKEND_DATABASE_CONNECTION during registration may + * call this. + * + * If username is NULL, bootstrapping superuser is used. + * If dbname is NULL, connection is made to no specific database; + * only shared catalogs can be accessed. + */ +extern void BackgroundWorkerInitializeConnection(char *dbname, char *username); + +#endif /* BGWORKER_H */ diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index 0fe7ec2..7ea4462 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -51,6 +51,8 @@ extern void ClosePostmasterPorts(bool am_syslogger); extern int MaxLivePostmasterChildren(void); +extern int GetNumRegisteredBackgroundWorkers(void); + #ifdef EXEC_BACKEND extern pid_t postmaster_forkexec(int argc, char *argv[]); extern void SubPostmasterMain(int argc, char *argv[]) __attribute__((noreturn));