bgworker-4.patch
application/octet-stream
Filename: bgworker-4.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| contrib/auth_counter/auth_counter.c | 203 | 0 |
| contrib/auth_counter/Makefile | 14 | 0 |
| contrib/worker_spi/Makefile | 14 | 0 |
| contrib/worker_spi/worker_spi.c | 177 | 0 |
| src/backend/postmaster/postmaster.c | 766 | 39 |
| src/backend/utils/init/globals.c | 1 | 0 |
| src/backend/utils/init/miscinit.c | 3 | 3 |
| src/backend/utils/init/postinit.c | 13 | 0 |
| src/backend/utils/misc/guc.c | 11 | 5 |
| src/include/miscadmin.h | 1 | 0 |
| src/include/postmaster/bgworker.h | 93 | 0 |
| src/include/postmaster/postmaster.h | 2 | 0 |
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 <unistd.h>
+
+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 <unistd.h>
+
+#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));