bgworker-7.patch
text/x-diff
Filename: bgworker-7.patch
Type: text/x-diff
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: context
| File | + | − |
|---|---|---|
| contrib/auth_counter/auth_counter.c | 230 | 0 |
| contrib/auth_counter/Makefile | 14 | 0 |
| contrib/worker_spi/Makefile | 14 | 0 |
| contrib/worker_spi/worker_spi.c | 263 | 0 |
| src/backend/postmaster/postmaster.c | 998 | 1 |
| src/backend/utils/init/globals.c | 1 | 0 |
| src/backend/utils/init/miscinit.c | 3 | 0 |
| src/backend/utils/init/postinit.c | 13 | 0 |
| src/backend/utils/misc/guc.c | 11 | 1 |
| src/include/miscadmin.h | 1 | 0 |
| src/include/postmaster/bgworker.h | 104 | 0 |
| src/include/postmaster/postmaster.h | 2 | 0 |
*** /dev/null
--- b/contrib/auth_counter/Makefile
***************
*** 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
*** /dev/null
--- b/contrib/auth_counter/auth_counter.c
***************
*** 0 ****
--- 1,230 ----
+ /* -------------------------------------------------------------------------
+ *
+ * auth_counter.c
+ *
+ * Copyright (C) 2012, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/auth_counter/auth_counter.c
+ *
+ * -------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ /* These are always necessary for a bgworker */
+ #include "miscadmin.h"
+ #include "postmaster/bgworker.h"
+ #include "storage/ipc.h"
+ #include "storage/latch.h"
+ #include "storage/lwlock.h"
+ #include "storage/proc.h"
+ #include "storage/shmem.h"
+
+ /* these headers are used by this particular worker's code */
+ #include "libpq/auth.h"
+ #include "utils/guc.h"
+ #include "utils/timestamp.h"
+
+ PG_MODULE_MAGIC;
+
+ #define WORKER_NAME "auth counter"
+
+ 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;
+
+ /* shared memory state */
+ typedef struct AuthCounterState
+ {
+ LWLockId lock;
+ long success;
+ long failure;
+ } AuthCounterState;
+
+ static AuthCounterState *ac;
+
+ /* State to be changed from signal handler */
+ static volatile bool terminate;
+
+ static void
+ auth_counter_sigterm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ terminate = true;
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ errno = save_errno;
+ }
+
+ /*
+ * auth_counter_main
+ *
+ * The main routine of this bgworker: logs the number of successful and failed
+ * authentication for each intervals, until receiving a signal.
+ */
+ static void
+ auth_counter_main(void *args)
+ {
+ terminate = false;
+
+ /* We're now ready to receive signals */
+ BackgroundWorkerUnblockSignals();
+
+ /*
+ * Initialize counter variables
+ */
+ LWLockAcquire(ac->lock, LW_EXCLUSIVE);
+ ac->success = 0;
+ ac->failure = 0;
+ LWLockRelease(ac->lock);
+
+ while (!terminate)
+ {
+ long n_success;
+ long n_failed;
+ int rc;
+
+ /*
+ * Background workers mustn't call sleep or any equivalent: instead,
+ * they wait on their process latch, which sleeps if necessary, but is
+ * awakened if postmaster dies. That way the background process goes
+ * away immediately in an emergency.
+ */
+ rc = WaitLatch(&MyProc->procLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+ auth_counter_interval * 1000L);
+ ResetLatch(&MyProc->procLatch);
+
+ /* emergency bailout if postmaster has died */
+ if (rc & WL_POSTMASTER_DEATH)
+ proc_exit(1);
+
+ LWLockAcquire(ac->lock, LW_EXCLUSIVE);
+ n_success = ac->success;
+ n_failed = ac->failure;
+ LWLockRelease(ac->lock);
+
+ elog(LOG, "%s (%d) %lu logins successful, %lu failed logins - %s",
+ WORKER_NAME, MyProcPid, n_success, n_failed,
+ timestamptz_to_str(GetCurrentTimestamp()));
+ }
+
+ proc_exit(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(ac->lock, LW_EXCLUSIVE);
+ if (status == STATUS_OK)
+ ac->success++;
+ else
+ ac->failure++;
+ LWLockRelease(ac->lock);
+ }
+
+ /*
+ * Callback just after shared memory allocation
+ */
+ static void
+ auth_counter_shmem_startup(void)
+ {
+ bool found;
+
+ if (shmem_startup_hook_next)
+ shmem_startup_hook_next();
+
+ /* reset in case this is a restart within the postmaster */
+ ac = NULL;
+
+ /*
+ * Allocate (or reattach to) the shared memory we need. Holding the
+ * AddinShmemInitLock is important for this, to avoid multiple processes
+ * from doing it concurrently.
+ */
+ LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+ ac = ShmemInitStruct(WORKER_NAME,
+ 2 * sizeof(long),
+ &found);
+
+ if (!found)
+ {
+ /* first time through: allocate lwlock to protect counters */
+ ac->lock = LWLockAssign();
+ }
+
+ /* Global shared initialization is complete; release this lock */
+ LWLockRelease(AddinShmemInitLock);
+
+ /*
+ * Now we can reset the module state, if appropriate. This can be done
+ * holding only the module's own lock.
+ */
+ if (!found)
+ {
+ LWLockAcquire(ac->lock, LW_EXCLUSIVE);
+
+ /* reset counters */
+ ac->success = 0;
+ ac->failure = 0;
+
+ LWLockRelease(ac->lock);
+ }
+ }
+
+ /*
+ * 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 shared memory and lock */
+ RequestAddinShmemSpace(2 * sizeof(long));
+ RequestAddinLWLocks(1);
+
+ shmem_startup_hook_next = shmem_startup_hook;
+ shmem_startup_hook = auth_counter_shmem_startup;
+
+ /* install our client auth hook */
+ original_client_auth_hook = ClientAuthentication_hook;
+ ClientAuthentication_hook = auth_counter_check;
+
+ /* register the worker process */
+ worker.bgw_name = WORKER_NAME;
+ worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
+ worker.bgw_start_time = BgWorkerStart_ConsistentState;
+ 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);
+ }
*** /dev/null
--- b/contrib/worker_spi/Makefile
***************
*** 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
*** /dev/null
--- b/contrib/worker_spi/worker_spi.c
***************
*** 0 ****
--- 1,263 ----
+ /* -------------------------------------------------------------------------
+ *
+ * worker_spi.c
+ * Sample background worker code that demonstrates usage of a database
+ * connection.
+ *
+ * This code connects to a database, create a schema and table, and summarizes
+ * the numbers contained therein. To see it working, insert an initial value
+ * with "total" type and some initial value; then insert some other rows with
+ * "delta" type. Delta rows will be deleted by this worker and their values
+ * aggregated into the total.
+ *
+ * Copyright (C) 2012, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/worker_spi/worker_spi.c
+ *
+ * -------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ /* These are always necessary for a bgworker */
+ #include "miscadmin.h"
+ #include "postmaster/bgworker.h"
+ #include "storage/ipc.h"
+ #include "storage/latch.h"
+ #include "storage/lwlock.h"
+ #include "storage/proc.h"
+ #include "storage/shmem.h"
+
+ /* these headers are used by this particular worker's code */
+ #include "access/xact.h"
+ #include "executor/spi.h"
+ #include "fmgr.h"
+ #include "lib/stringinfo.h"
+ #include "utils/builtins.h"
+ #include "utils/snapmgr.h"
+
+ PG_MODULE_MAGIC;
+
+ void _PG_init(void);
+
+ static bool got_sigterm = false;
+
+
+ typedef struct worktable
+ {
+ const char *schema;
+ const char *name;
+ } worktable;
+
+ static void
+ worker_spi_sigterm(SIGNAL_ARGS)
+ {
+ int save_errno = errno;
+
+ got_sigterm = true;
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+
+ errno = save_errno;
+ }
+
+ static void
+ worker_spi_sighup(SIGNAL_ARGS)
+ {
+ elog(LOG, "got sighup!");
+ if (MyProc)
+ SetLatch(&MyProc->procLatch);
+ }
+
+ static void
+ initialize_worker_spi(worktable *table)
+ {
+ 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'",
+ table->schema);
+
+ 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");
+
+ if (ntup == 0)
+ {
+ resetStringInfo(&buf);
+ appendStringInfo(&buf,
+ "CREATE SCHEMA \"%s\" "
+ "CREATE TABLE \"%s\" ("
+ " type text CHECK (type IN ('total', 'delta')), "
+ " value integer)"
+ "CREATE UNIQUE INDEX \"%s_unique_total\" ON \"%s\" (type) "
+ "WHERE type = 'total'",
+ table->schema, table->name, table->name, table->name);
+
+ 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)
+ {
+ worktable *table = (worktable *) main_arg;
+ StringInfoData buf;
+
+ /* We're now ready to receive signals */
+ BackgroundWorkerUnblockSignals();
+
+ /* Connect to our database */
+ BackgroundWorkerInitializeConnection("postgres", NULL);
+
+ elog(LOG, "%s initialized with %s.%s",
+ MyBgworkerEntry->bgw_name, table->schema, table->name);
+ initialize_worker_spi(table);
+
+ /*
+ * Quote identifiers passed to us. Note that this must be done after
+ * initialize_worker_spi, because that routine assumes the names are not
+ * quoted.
+ *
+ * Note some memory might be leaked here.
+ */
+ table->schema = quote_identifier(table->schema);
+ table->name = quote_identifier(table->name);
+
+ 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",
+ table->schema, table->name,
+ table->schema, table->name,
+ table->name,
+ table->name);
+
+ while (!got_sigterm)
+ {
+ int ret;
+ int rc;
+
+ /*
+ * Background workers mustn't call usleep() or any direct equivalent:
+ * instead, they may wait on their process latch, which sleeps as
+ * necessary, but is awakened if postmaster dies. That way the
+ * background process goes away immediately in an emergency.
+ */
+ rc = WaitLatch(&MyProc->procLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+ 1000L);
+ ResetLatch(&MyProc->procLatch);
+
+ /* emergency bailout if postmaster has died */
+ if (rc & WL_POSTMASTER_DEATH)
+ proc_exit(1);
+
+ 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.%s: error code %d",
+ table->schema, table->name, 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, "%s: count in %s.%s is now %d",
+ MyBgworkerEntry->bgw_name,
+ table->schema, table->name, val);
+ }
+
+ SPI_finish();
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ }
+
+ proc_exit(0);
+ }
+
+ /*
+ * Entrypoint of this module.
+ *
+ * We register two worker processes here, to demonstrate how that can be done.
+ */
+ void
+ _PG_init(void)
+ {
+ BackgroundWorker worker;
+ worktable *table;
+
+ /* register the worker processes. These values are common for both */
+ 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;
+
+ /*
+ * These values are used for the first worker.
+ *
+ * Note these are palloc'd. The reason this works after starting a new
+ * worker process is that if we only fork, they point to valid allocated
+ * memory in the child process; and if we fork and then exec, the exec'd
+ * process will run this code again, and so the memory is also valid there.
+ */
+ table = palloc(sizeof(worktable));
+ table->schema = pstrdup("schema1");
+ table->name = pstrdup("counted");
+
+ worker.bgw_name = "SPI worker 1";
+ worker.bgw_restart_time = BGW_NEVER_RESTART;
+ worker.bgw_main_arg = (void *) table;
+ RegisterBackgroundWorker(&worker);
+
+ /* Values for the second worker */
+ table = palloc(sizeof(worktable));
+ table->schema = pstrdup("our schema2");
+ table->name = pstrdup("counted rows");
+
+ worker.bgw_name = "SPI worker 2";
+ worker.bgw_restart_time = 2;
+ worker.bgw_main_arg = (void *) table;
+ RegisterBackgroundWorker(&worker);
+ }
*** a/src/backend/postmaster/postmaster.c
--- b/src/backend/postmaster/postmaster.c
***************
*** 103,108 ****
--- 103,109 ----
#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,138 ****
* 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
* 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,
--- 133,140 ----
* 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, 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,150 **** 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? */
bool dead_end; /* is it going to send an error and quit? */
dlist_node elem; /* list link in BackendList */
} Backend;
--- 146,157 ----
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 */
! 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? */
dlist_node elem; /* list link in BackendList */
} Backend;
***************
*** 155,160 **** static dlist_head BackendList = DLIST_STATIC_INIT(BackendList);
--- 162,190 ----
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 */
+ #ifdef EXEC_BACKEND
+ int cookie;
+ #endif
+ slist_node lnode; /* list link */
+ } RegisteredBgWorker;
+
+ static slist_head BackgroundWorkerList = SLIST_STATIC_INIT(BackgroundWorkerList);
+
+ BackgroundWorker *MyBgworkerEntry = NULL;
+
+
+
/* The socket number we are listening for connections on */
int PostPortNumber;
/* The directory names for Unix socket(s) */
***************
*** 306,311 **** static volatile sig_atomic_t start_autovac_launcher = false;
--- 336,345 ----
/* 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,348 **** static void startup_die(SIGNAL_ARGS);
--- 377,384 ----
static void dummy_handler(SIGNAL_ARGS);
static void StartupPacketTimeoutHandler(void);
static void CleanupBackend(int pid, int exitstatus);
+ static bool CleanupBackgroundWorker(int pid, int exitstatus);
+ static void do_start_bgworker(void);
static void HandleChildCrash(int pid, int exitstatus, const char *procname);
static void LogChildExit(int lev, const char *procname,
int pid, int exitstatus);
***************
*** 361,366 **** static long PostmasterRandom(void);
--- 397,403 ----
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,379 **** 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 */
static int CountChildren(int target);
static bool CreateOptsFile(int argc, char *argv[], char *fullprogname);
static pid_t StartChildProcess(AuxProcType type);
static void StartAutovacuumWorker(void);
--- 408,421 ----
#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_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);
***************
*** 473,478 **** static bool save_backend_variables(BackendParameters *param, Port *port,
--- 515,522 ----
static void ShmemBackendArrayAdd(Backend *bn);
static void ShmemBackendArrayRemove(Backend *bn);
+
+ static BackgroundWorker *find_bgworker_entry(int cookie);
#endif /* EXEC_BACKEND */
#define StartupDataBase() StartChildProcess(StartupProcess)
***************
*** 1087,1093 **** 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.
*/
pqinitmask();
PG_SETMASK(&BlockSig);
--- 1131,1138 ----
* 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, postmaster/bgworker.c and
! * postmaster/checkpointer.c.
*/
pqinitmask();
PG_SETMASK(&BlockSig);
***************
*** 1177,1182 **** PostmasterMain(int argc, char *argv[])
--- 1222,1230 ----
Assert(StartupPID != 0);
pmState = PM_STARTUP;
+ /* Some workers may be scheduled to start now */
+ StartOneBackgroundWorker();
+
status = ServerLoop();
/*
***************
*** 1342,1347 **** checkDataDir(void)
--- 1390,1479 ----
}
/*
+ * Determine how long should we let ServerLoop sleep.
+ *
+ * In normal conditions we wait at most one minute, to ensure that the other
+ * background tasks handled by ServerLoop 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.
+ */
+ static void
+ DetermineSleepTime(struct timeval *timeout)
+ {
+ TimestampTz next_wakeup = 0;
+
+ /*
+ * Normal case: either there are no background workers at all, or we're in
+ * a shutdown sequence (during which we ignore bgworkers altogether).
+ */
+ if (Shutdown > NoShutdown ||
+ (!StartWorkerNeeded && !HaveCrashedWorker))
+ {
+ timeout->tv_sec = 60;
+ timeout->tv_usec = 0;
+ return;
+ }
+
+ if (StartWorkerNeeded)
+ {
+ timeout->tv_sec = 0;
+ timeout->tv_usec = 0;
+ return;
+ }
+
+ if (HaveCrashedWorker)
+ {
+ slist_iter siter;
+
+ /*
+ * When there are crashed bgworkers, we sleep just long enough that
+ * they are restarted when they request to be. Scan the list to
+ * determine the minimum of all wakeup times according to most
+ * recent crash time and requested restart interval.
+ */
+ slist_foreach(siter, &BackgroundWorkerList)
+ {
+ RegisteredBgWorker *rw;
+ TimestampTz this_wakeup;
+
+ rw = slist_container(RegisteredBgWorker, lnode, siter.cur);
+
+ if (rw->crashed_at == 0)
+ continue;
+
+ if (rw->worker->bgw_restart_time == BGW_NEVER_RESTART)
+ continue;
+
+ this_wakeup = TimestampTzPlusMilliseconds(rw->crashed_at,
+ 1000L * rw->worker->bgw_restart_time);
+ if (next_wakeup == 0 || this_wakeup < next_wakeup)
+ next_wakeup = this_wakeup;
+ }
+ }
+
+ if (next_wakeup != 0)
+ {
+ int microsecs;
+
+ TimestampDifference(GetCurrentTimestamp(), next_wakeup,
+ &timeout->tv_sec, µsecs);
+ timeout->tv_usec = microsecs;
+
+ /* Ensure we don't exceed one minute */
+ if (timeout->tv_sec > 60)
+ {
+ timeout->tv_sec = 60;
+ timeout->tv_usec = 0;
+ }
+ }
+ else
+ {
+ timeout->tv_sec = 60;
+ timeout->tv_usec = 0;
+ }
+ }
+
+ /*
* Main idle loop of postmaster
*/
static int
***************
*** 1364,1372 **** 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.
--- 1496,1501 ----
***************
*** 1385,1392 **** ServerLoop(void)
/* must set timeout each time; some OSes change it! */
struct timeval timeout;
! timeout.tv_sec = 60;
! timeout.tv_usec = 0;
selres = select(nSockets, &rmask, NULL, NULL, &timeout);
}
--- 1514,1520 ----
/* must set timeout each time; some OSes change it! */
struct timeval timeout;
! DetermineSleepTime(&timeout);
selres = select(nSockets, &rmask, NULL, NULL, &timeout);
}
***************
*** 1498,1503 **** ServerLoop(void)
--- 1626,1635 ----
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
***************
*** 1513,1519 **** ServerLoop(void)
}
}
-
/*
* Initialise the masks for select() for the ports we are listening on.
* Return the number of sockets to listen on.
--- 1645,1650 ----
***************
*** 2205,2212 **** 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);
/* and the autovac launcher too */
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGTERM);
--- 2336,2346 ----
if (pmState == PM_RUN || pmState == PM_RECOVERY ||
pmState == PM_HOT_STANDBY || pmState == PM_STARTUP)
{
! /* 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);
***************
*** 2258,2269 **** pmdie(SIGNAL_ARGS)
signal_child(BgWriterPID, SIGTERM);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, 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.
*/
pmState = PM_WAIT_BACKENDS;
}
--- 2392,2405 ----
signal_child(BgWriterPID, SIGTERM);
if (WalReceiverPID != 0)
signal_child(WalReceiverPID, SIGTERM);
+ SignalUnconnectedWorkers(SIGTERM);
if (pmState == PM_RECOVERY)
{
/*
! * 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;
}
***************
*** 2275,2283 **** pmdie(SIGNAL_ARGS)
{
ereport(LOG,
(errmsg("aborting any active transactions")));
! /* shut down all backends and autovac workers */
SignalSomeChildren(SIGTERM,
! BACKEND_TYPE_NORMAL | BACKEND_TYPE_AUTOVAC);
/* and the autovac launcher too */
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGTERM);
--- 2411,2420 ----
{
ereport(LOG,
(errmsg("aborting any active transactions")));
! /* shut down all backends and workers */
SignalSomeChildren(SIGTERM,
! BACKEND_TYPE_NORMAL | BACKEND_TYPE_AUTOVAC |
! BACKEND_TYPE_BGWORKER);
/* and the autovac launcher too */
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGTERM);
***************
*** 2321,2326 **** pmdie(SIGNAL_ARGS)
--- 2458,2464 ----
signal_child(PgArchPID, SIGQUIT);
if (PgStatPID != 0)
signal_child(PgStatPID, SIGQUIT);
+ SignalUnconnectedWorkers(SIGQUIT);
ExitPostmaster(0);
break;
}
***************
*** 2449,2454 **** reaper(SIGNAL_ARGS)
--- 2587,2595 ----
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")));
***************
*** 2615,2620 **** reaper(SIGNAL_ARGS)
--- 2756,2769 ----
continue;
}
+ /* Was it one of our background workers? */
+ if (CleanupBackgroundWorker(pid, exitstatus))
+ {
+ /* have it be restarted */
+ HaveCrashedWorker = true;
+ continue;
+ }
+
/*
* Else do standard backend child cleanup.
*/
***************
*** 2633,2643 **** reaper(SIGNAL_ARGS)
--- 2782,2881 ----
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. One important difference is that
+ * we don't know yet that the dying process is a bgworker, so we must be silent
+ * until we're sure it is.
+ */
+ static bool
+ CleanupBackgroundWorker(int pid,
+ int exitstatus) /* child's exit status */
+ {
+ char namebuf[MAXPGPATH];
+ slist_iter iter;
+
+ slist_foreach(iter, &BackgroundWorkerList)
+ {
+ RegisteredBgWorker *rw;
+
+ rw = slist_container(RegisteredBgWorker, lnode, iter.cur);
+
+ if (rw->pid != pid)
+ continue;
+
+ #ifdef WIN32
+ /* see CleanupBackend */
+ if (exitstatus == ERROR_WAIT_NO_CHILDREN)
+ exitstatus = 0;
+ #endif
+
+ snprintf(namebuf, MAXPGPATH, "%s: %s", _("worker process"),
+ rw->worker->bgw_name);
+
+ /*
+ * For shared-memory-connected workers, just like a backend, any exit
+ * status other than 0 or 1 is considered a crash and causes a
+ * system-wide restart.
+ */
+ if (rw->worker->bgw_flags & BGWORKER_SHMEM_ACCESS)
+ {
+ if (!EXIT_STATUS_0(exitstatus) && !EXIT_STATUS_1(exitstatus))
+ {
+ 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)
+ {
+ dlist_delete(&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))
+ {
+ HaveCrashedWorker = true;
+ 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,
***************
*** 2705,2711 **** CleanupBackend(int pid,
/*
* HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer,
! * walwriter or autovacuum.
*
* The objectives here are to clean up our local state about the child
* process, and to signal all other remaining children to quickdie.
--- 2943,2949 ----
/*
* HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer,
! * 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.
***************
*** 2714,2719 **** static void
--- 2952,2958 ----
HandleChildCrash(int pid, int exitstatus, const char *procname)
{
dlist_mutable_iter iter;
+ slist_iter siter;
Backend *bp;
/*
***************
*** 2727,2732 **** HandleChildCrash(int pid, int exitstatus, const char *procname)
--- 2966,3019 ----
(errmsg("terminating any other active server processes")));
}
+ /* Process unconnected background workers */
+ slist_foreach(siter, &BackgroundWorkerList)
+ {
+ RegisteredBgWorker *rw;
+
+ rw = slist_container(RegisteredBgWorker, lnode, siter.cur);
+ if (rw->pid == pid)
+ {
+ /*
+ * Found entry for freshly-dead worker, so remove it.
+ */
+ (void) ReleasePostmasterChildSlot(rw->child_slot);
+ if (rw->backend)
+ {
+ dlist_delete(&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 */
dlist_foreach_modify(iter, &BackendList)
{
***************
*** 2761,2767 **** HandleChildCrash(int pid, int exitstatus, const char *procname)
--- 3048,3060 ----
*
* We could exclude dead_end children here, but at least in the
* SIGSTOP case it seems better to include them.
+ *
+ * Background workers were already processed above; ignore them
+ * here.
*/
+ if (bp->bkend_type == BACKEND_TYPE_BGWORKER)
+ continue;
+
if (!FatalError)
{
ereport(DEBUG2,
***************
*** 3005,3011 **** PostmasterStateMachine(void)
{
/*
* PM_WAIT_BACKENDS state ends when we have no regular backends
! * (including autovac workers) 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
--- 3298,3305 ----
{
/*
* PM_WAIT_BACKENDS state ends when we have no regular backends
! * (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
***************
*** 3014,3020 **** PostmasterStateMachine(void)
* later after writing the checkpoint record, like the archiver
* process.
*/
! if (CountChildren(BACKEND_TYPE_NORMAL | BACKEND_TYPE_AUTOVAC) == 0 &&
StartupPID == 0 &&
WalReceiverPID == 0 &&
BgWriterPID == 0 &&
--- 3308,3315 ----
* later after writing the checkpoint record, like the archiver
* process.
*/
! if (CountChildren(BACKEND_TYPE_NORMAL | BACKEND_TYPE_WORKER) == 0 &&
! CountUnconnectedWorkers() == 0 &&
StartupPID == 0 &&
WalReceiverPID == 0 &&
BgWriterPID == 0 &&
***************
*** 3227,3232 **** signal_child(pid_t pid, int signal)
--- 3522,3558 ----
}
/*
+ * Send a signal to bgworkers that did not request backend connections
+ */
+ static bool
+ SignalUnconnectedWorkers(int signal)
+ {
+ bool signaled = false;
+ slist_iter iter;
+
+ slist_foreach(iter, &BackgroundWorkerList)
+ {
+ RegisteredBgWorker *rw;
+
+ rw = slist_container(RegisteredBgWorker, lnode, iter.cur);
+
+ 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).
*/
***************
*** 3249,3263 **** SignalSomeChildren(int signal, int target)
*/
if (target != BACKEND_TYPE_ALL)
{
! int child;
! 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))
continue;
}
--- 3575,3589 ----
*/
if (target != BACKEND_TYPE_ALL)
{
! /*
! * 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 (!(target & bp->bkend_type))
continue;
}
***************
*** 3375,3381 **** BackendStartup(Port *port)
* of backends.
*/
bn->pid = pid;
! bn->is_autovacuum = false;
dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
--- 3701,3707 ----
* of backends.
*/
bn->pid = pid;
! bn->bkend_type = BACKEND_TYPE_NORMAL; /* Can change later to WALSND */
dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
***************
*** 3744,3750 **** 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 */
mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
--- 4070,4079 ----
fp = AllocateFile(tmpfilename, PG_BINARY_W);
if (!fp)
{
! /*
! * As in OpenTemporaryFileInTablespace, try to make the temp-file
! * directory
! */
mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
fp = AllocateFile(tmpfilename, PG_BINARY_W);
***************
*** 4078,4084 **** SubPostmasterMain(int argc, char *argv[])
if (strcmp(argv[1], "--forkbackend") == 0 ||
strcmp(argv[1], "--forkavlauncher") == 0 ||
strcmp(argv[1], "--forkavworker") == 0 ||
! strcmp(argv[1], "--forkboot") == 0)
PGSharedMemoryReAttach();
/* autovacuum needs this set before calling InitProcess */
--- 4407,4414 ----
if (strcmp(argv[1], "--forkbackend") == 0 ||
strcmp(argv[1], "--forkavlauncher") == 0 ||
strcmp(argv[1], "--forkavworker") == 0 ||
! strcmp(argv[1], "--forkboot") == 0 ||
! strncmp(argv[1], "--forkbgworker", 14) == 0)
PGSharedMemoryReAttach();
/* autovacuum needs this set before calling InitProcess */
***************
*** 4213,4218 **** SubPostmasterMain(int argc, char *argv[])
--- 4543,4568 ----
AutoVacWorkerMain(argc - 2, argv + 2); /* does not return */
}
+ if (strncmp(argv[1], "--forkbgworker=", 15) == 0)
+ {
+ int cookie;
+
+ /* Close the postmaster's sockets */
+ ClosePostmasterPorts(false);
+
+ /* Restore basic shared memory pointers */
+ InitShmemAccess(UsedShmemSegAddr);
+
+ /* Need a PGPROC to run CreateSharedMemoryAndSemaphores */
+ InitProcess();
+
+ /* Attach process to shared data structures */
+ CreateSharedMemoryAndSemaphores(false, 0);
+
+ cookie = atoi(argv[1] + 15);
+ MyBgworkerEntry = find_bgworker_entry(cookie);
+ do_start_bgworker();
+ }
if (strcmp(argv[1], "--forkarch") == 0)
{
/* Close the postmaster's sockets */
***************
*** 4312,4317 **** sigusr1_handler(SIGNAL_ARGS)
--- 4662,4670 ----
(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) &&
***************
*** 4479,4484 **** PostmasterRandom(void)
--- 4832,4863 ----
}
/*
+ * Count up number of worker processes that did not request backend connections
+ */
+ static int
+ CountUnconnectedWorkers(void)
+ {
+ slist_iter iter;
+ int cnt = 0;
+
+ slist_foreach(iter, &BackgroundWorkerList)
+ {
+ RegisteredBgWorker *rw;
+
+ rw = slist_container(RegisteredBgWorker, lnode, iter.cur);
+
+ 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).
*/
***************
*** 4501,4515 **** CountChildren(int target)
*/
if (target != BACKEND_TYPE_ALL)
{
! int child;
! 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))
continue;
}
--- 4880,4894 ----
*/
if (target != BACKEND_TYPE_ALL)
{
! /*
! * 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 (!(target & bp->bkend_type))
continue;
}
***************
*** 4668,4674 **** StartAutovacuumWorker(void)
bn->pid = StartAutoVacWorker();
if (bn->pid > 0)
{
! bn->is_autovacuum = true;
dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
ShmemBackendArrayAdd(bn);
--- 5047,5053 ----
bn->pid = StartAutoVacWorker();
if (bn->pid > 0)
{
! bn->bkend_type = BACKEND_TYPE_AUTOVAC;
dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
ShmemBackendArrayAdd(bn);
***************
*** 4743,4749 **** 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,
* 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
--- 5122,5129 ----
*
* 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, 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
***************
*** 4755,4760 **** MaxLivePostmasterChildren(void)
--- 5135,5717 ----
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;
+ int namelen = strlen(worker->bgw_name);
+ #ifdef EXEC_BACKEND
+ static int BackgroundWorkerCookie = 0;
+ #endif
+
+ if (!IsUnderPostmaster)
+ ereport(LOG,
+ (errmsg("registering background worker: %s", worker->bgw_name)));
+
+ if (!process_shared_preload_libraries_in_progress)
+ {
+ if (!IsUnderPostmaster)
+ ereport(LOG,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("background worker \"%s\": must be registered in shared_preload_libraries",
+ worker->bgw_name)));
+ return;
+ }
+
+ /* sanity check for flags */
+ if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
+ {
+ if (!(worker->bgw_flags & BGWORKER_SHMEM_ACCESS))
+ {
+ if (!IsUnderPostmaster)
+ ereport(LOG,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("background worker \"%s\": must attach to shared memory in order to request a database connection",
+ worker->bgw_name)));
+ return;
+ }
+
+ if (worker->bgw_start_time == BgWorkerStart_PostmasterStart)
+ {
+ if (!IsUnderPostmaster)
+ ereport(LOG,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("background worker \"%s\": cannot request database access if starting at postmaster start",
+ worker->bgw_name)));
+ return;
+ }
+
+ /* XXX other checks? */
+ }
+
+ if ((worker->bgw_restart_time < 0 &&
+ worker->bgw_restart_time != BGW_NEVER_RESTART) ||
+ (worker->bgw_restart_time > USECS_PER_DAY / 1000))
+ {
+ if (!IsUnderPostmaster)
+ ereport(LOG,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("background worker \"%s\": invalid restart interval",
+ worker->bgw_name)));
+ return;
+ }
+
+ /*
+ * Copy the registration data into the registered workers list.
+ */
+ rw = malloc(MAXALIGN(sizeof(RegisteredBgWorker)) +
+ MAXALIGN(sizeof(BackgroundWorker)) + namelen + 1);
+ 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, namelen + 1);
+
+ rw->backend = NULL;
+ rw->pid = 0;
+ rw->child_slot = 0;
+ rw->crashed_at = 0;
+ #ifdef EXEC_BACKEND
+ rw->cookie = BackgroundWorkerCookie++;
+ #endif
+
+ slist_push_head(&BackgroundWorkerList, &rw->lnode);
+ }
+
+ /*
+ * Connect background worker to a database.
+ */
+ void
+ BackgroundWorkerInitializeConnection(char *dbname, char *username)
+ {
+ BackgroundWorker *worker = MyBgworkerEntry;
+
+ /* XXX is this the right errcode? */
+ if (!(worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION))
+ ereport(FATAL,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("database connection requirement not indicated during registration")));
+
+ 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);
+ }
+
+ /*
+ * Block/unblock signals in a background worker
+ */
+ void
+ BackgroundWorkerBlockSignals(void)
+ {
+ PG_SETMASK(&BlockSig);
+ }
+
+ void
+ BackgroundWorkerUnblockSignals(void)
+ {
+ PG_SETMASK(&UnBlockSig);
+ }
+
+ #ifdef EXEC_BACKEND
+ static BackgroundWorker *
+ find_bgworker_entry(int cookie)
+ {
+ slist_iter iter;
+
+ slist_foreach(iter, &BackgroundWorkerList)
+ {
+ RegisteredBgWorker *rw;
+
+ rw = slist_container(RegisteredBgWorker, lnode, iter.cur);
+ if (rw->cookie == cookie)
+ return rw->worker;
+ }
+
+ return NULL;
+ }
+ #endif
+
+ static void
+ bgworker_quickdie(SIGNAL_ARGS)
+ {
+ sigaddset(&BlockSig, SIGQUIT); /* prevent nested calls */
+ PG_SETMASK(&BlockSig);
+
+ /*
+ * We DO NOT want to run proc_exit() callbacks -- we're here because
+ * shared memory may be corrupted, so we don't want to try to clean up our
+ * transaction. Just nail the windows shut and get out of town. Now that
+ * there's an atexit callback to prevent third-party code from breaking
+ * things by calling exit() directly, we have to reset the callbacks
+ * explicitly to make this work as intended.
+ */
+ on_exit_reset();
+
+ /*
+ * Note we do exit(0) here, not exit(2) like quickdie. The reason is that
+ * we don't want to be seen this worker as independently crashed, because
+ * then postmaster would delay restarting it again afterwards. If some
+ * idiot DBA manually sends SIGQUIT to a random bgworker, the "dead man
+ * switch" will ensure that postmaster sees this as a crash.
+ */
+ exit(0);
+ }
+
+ static void
+ do_start_bgworker(void)
+ {
+ sigjmp_buf local_sigjmp_buf;
+ char buf[MAXPGPATH];
+ BackgroundWorker *worker = MyBgworkerEntry;
+
+ if (worker == NULL)
+ elog(FATAL, "unable to find bgworker entry");
+
+ /* we are a postmaster subprocess now */
+ IsUnderPostmaster = true;
+ IsBackgroundWorker = true;
+
+ /* reset MyProcPid */
+ MyProcPid = getpid();
+
+ /* record Start Time for logging */
+ MyStartTime = time(NULL);
+
+ /* Identify myself via ps */
+ snprintf(buf, MAXPGPATH, "bgworker: %s", worker->bgw_name);
+ init_ps_display(buf, "", "", "");
+
+ SetProcessingMode(InitProcessing);
+
+ /* Apply PostAuthDelay */
+ if (PostAuthDelay > 0)
+ pg_usleep(PostAuthDelay * 1000000L);
+
+ /*
+ * If possible, make this process a group leader, so that the postmaster
+ * can signal any child processes too.
+ */
+ #ifdef HAVE_SETSID
+ if (setsid() < 0)
+ elog(FATAL, "setsid() failed: %m");
+ #endif
+
+ /*
+ * Set up signal handlers.
+ */
+ if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
+ {
+ /*
+ * SIGINT is used to signal canceling the current action
+ */
+ pqsignal(SIGINT, StatementCancelHandler);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ pqsignal(SIGFPE, FloatExceptionHandler);
+
+ /* XXX Any other handlers needed here? */
+ }
+ else
+ {
+ pqsignal(SIGINT, SIG_IGN);
+ pqsignal(SIGUSR1, SIG_IGN);
+ pqsignal(SIGFPE, SIG_IGN);
+ }
+
+ /* These are configurable */
+ pqsignal(SIGTERM, worker->bgw_sigterm);
+ pqsignal(SIGHUP, worker->bgw_sighup);
+
+ pqsignal(SIGQUIT, bgworker_quickdie);
+ InitializeTimeouts(); /* establishes SIGALRM handler */
+
+ pqsignal(SIGPIPE, SIG_IGN);
+ pqsignal(SIGUSR2, SIG_IGN);
+ pqsignal(SIGCHLD, SIG_DFL);
+
+ /*
+ * If an exception is encountered, processing resumes here.
+ *
+ * See notes in postgres.c about the design of this coding.
+ */
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0)
+ {
+ /* Since not using PG_TRY, must reset error stack by hand */
+ error_context_stack = NULL;
+
+ /* Prevent interrupts while cleaning up */
+ HOLD_INTERRUPTS();
+
+ /* Report the error to the server log */
+ EmitErrorReport();
+
+ /*
+ * 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)
+ {
+ slist_iter iter;
+ int count = 0;
+
+ slist_foreach(iter, &BackgroundWorkerList)
+ {
+ count++;
+ }
+
+ return count;
+ }
+
+ #ifdef EXEC_BACKEND
+ static pid_t
+ bgworker_forkexec(int cookie)
+ {
+ char *av[10];
+ int ac = 0;
+ char forkav[MAXPGPATH];
+
+ snprintf(forkav, MAXPGPATH, "--forkbgworker=%d", cookie);
+
+ av[ac++] = "postgres";
+ av[ac++] = forkav;
+ av[ac++] = NULL; /* filled in by postmaster_forkexec */
+ av[ac] = NULL;
+
+ Assert(ac < lengthof(av));
+
+ return postmaster_forkexec(ac, av);
+ }
+ #endif
+
+ /*
+ * 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(rw->cookie)))
+ #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 */
+
+ MyBgworkerEntry = rw->worker;
+ do_start_bgworker();
+ break;
+ #endif
+ default:
+ 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, but don't
+ * add it to the list of backends just yet.
+ *
+ * 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;
+
+ 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)
+ {
+ slist_iter iter;
+ TimestampTz now = 0;
+
+ if (FatalError)
+ {
+ StartWorkerNeeded = false;
+ HaveCrashedWorker = false;
+ return; /* not yet */
+ }
+
+ HaveCrashedWorker = false;
+
+ slist_foreach(iter, &BackgroundWorkerList)
+ {
+ RegisteredBgWorker *rw;
+
+ rw = slist_container(RegisteredBgWorker, lnode, iter.cur);
+
+ /* already running? */
+ if (rw->pid != 0)
+ continue;
+
+ /*
+ * If this worker has crashed previously, maybe it needs to be
+ * restarted (unless on registration it specified it doesn't want to be
+ * restarted at all). Check how long ago did a crash last happen. If
+ * the last crash is too recent, don't start it right away; let it be
+ * restarted once enough time has passed.
+ */
+ if (rw->crashed_at != 0)
+ {
+ if (rw->worker->bgw_restart_time == BGW_NEVER_RESTART)
+ continue;
+
+ if (now == 0)
+ now = GetCurrentTimestamp();
+
+ if (!TimestampDifferenceExceeds(rw->crashed_at, now,
+ rw->worker->bgw_restart_time * 1000))
+ {
+ 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 */
+
+ if (rw->backend)
+ {
+ dlist_push_head(&BackendList, &rw->backend->elem);
+ #ifdef EXEC_BACKEND
+ ShmemBackendArrayAdd(rw->backend);
+ #endif
+ }
+
+ /*
+ * 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
*** a/src/backend/utils/init/globals.c
--- b/src/backend/utils/init/globals.c
***************
*** 87,92 **** pid_t PostmasterPid = 0;
--- 87,93 ----
bool IsPostmasterEnvironment = false;
bool IsUnderPostmaster = false;
bool IsBinaryUpgrade = false;
+ bool IsBackgroundWorker = false;
bool ExitOnAnyError = false;
*** a/src/backend/utils/init/miscinit.c
--- b/src/backend/utils/init/miscinit.c
***************
*** 498,507 **** void
InitializeSessionUserIdStandalone(void)
{
/*
! * This function should only be called in single-user mode and in
! * autovacuum workers.
*/
! AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess());
/* call only once */
AssertState(!OidIsValid(AuthenticatedUserId));
--- 498,507 ----
InitializeSessionUserIdStandalone(void)
{
/*
! * This function should only be called in single-user mode, in
! * autovacuum workers, and in background workers.
*/
! AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsBackgroundWorker);
/* call only once */
AssertState(!OidIsValid(AuthenticatedUserId));
*** a/src/backend/utils/init/postinit.c
--- b/src/backend/utils/init/postinit.c
***************
*** 627,632 **** InitPostgres(const char *in_dbname, Oid dboid, const char *username,
--- 627,645 ----
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 */
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
***************
*** 52,57 ****
--- 52,58 ----
#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,114 ****
* 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).
*/
#define MAX_BACKENDS 0x7fffff
--- 109,116 ----
* 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 the number of bgworkers plus autovacuum_max_workers plus one (for the
! * autovacuum launcher).
*/
#define MAX_BACKENDS 0x7fffff
***************
*** 8628,8634 **** show_tcp_keepalives_count(void)
static bool
check_maxconnections(int *newval, void **extra, GucSource source)
{
! if (*newval + autovacuum_max_workers + 1 > MAX_BACKENDS)
return false;
return true;
}
--- 8630,8637 ----
static bool
check_maxconnections(int *newval, void **extra, GucSource source)
{
! if (*newval + GetNumRegisteredBackgroundWorkers() +
! autovacuum_max_workers + 1 > MAX_BACKENDS)
return false;
return true;
}
***************
*** 8636,8648 **** check_maxconnections(int *newval, void **extra, GucSource source)
static void
assign_maxconnections(int newval, void *extra)
{
! MaxBackends = newval + autovacuum_max_workers + 1;
}
static bool
check_autovacuum_max_workers(int *newval, void **extra, GucSource source)
{
! if (MaxConnections + *newval + 1 > MAX_BACKENDS)
return false;
return true;
}
--- 8639,8653 ----
static void
assign_maxconnections(int newval, void *extra)
{
! MaxBackends = newval + autovacuum_max_workers + 1 +
! GetNumRegisteredBackgroundWorkers();
}
static bool
check_autovacuum_max_workers(int *newval, void **extra, GucSource source)
{
! if (MaxConnections + *newval + 1 + GetNumRegisteredBackgroundWorkers() >
! MAX_BACKENDS)
return false;
return true;
}
***************
*** 8650,8656 **** check_autovacuum_max_workers(int *newval, void **extra, GucSource source)
static void
assign_autovacuum_max_workers(int newval, void *extra)
{
! MaxBackends = MaxConnections + newval + 1;
}
static bool
--- 8655,8662 ----
static void
assign_autovacuum_max_workers(int newval, void *extra)
{
! MaxBackends = MaxConnections + newval + 1 +
! GetNumRegisteredBackgroundWorkers();
}
static bool
*** a/src/include/miscadmin.h
--- b/src/include/miscadmin.h
***************
*** 131,136 **** do { \
--- 131,137 ----
extern pid_t PostmasterPid;
extern bool IsPostmasterEnvironment;
extern PGDLLIMPORT bool IsUnderPostmaster;
+ extern bool IsBackgroundWorker;
extern bool IsBinaryUpgrade;
extern bool ExitOnAnyError;
*** /dev/null
--- b/src/include/postmaster/bgworker.h
***************
*** 0 ****
--- 1,104 ----
+ /*--------------------------------------------------------------------
+ * 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;
+
+ #define BGW_DEFAULT_RESTART_INTERVAL 60
+ #define BGW_NEVER_RESTART -1
+
+ typedef struct BackgroundWorker
+ {
+ char *bgw_name;
+ int bgw_flags;
+ BgWorkerStartTime bgw_start_time;
+ int bgw_restart_time; /* in seconds, or BGW_NEVER_RESTART */
+ 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);
+
+ /* This is valid in a running worker */
+ extern BackgroundWorker *MyBgworkerEntry;
+
+ /*
+ * 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);
+
+ /* Block/unblock signals in a background worker process */
+ extern void BackgroundWorkerBlockSignals(void);
+ extern void BackgroundWorkerUnblockSignals(void);
+
+ #endif /* BGWORKER_H */
*** a/src/include/postmaster/postmaster.h
--- b/src/include/postmaster/postmaster.h
***************
*** 51,56 **** extern void ClosePostmasterPorts(bool am_syslogger);
--- 51,58 ----
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));