Make worker_spi sample code more complete

Make use of some GUC variables, and add SIGHUP handling to reload
the config file.  Patch submitted by Guillaume Lelarge.

Also, report to pg_stat_activity.  Per report from Marc Cousin, add
setting of statement start time.
This commit is contained in:
Alvaro Herrera 2013-04-10 13:29:25 -03:00
parent 66c01707c6
commit e543631f3c
1 changed files with 118 additions and 31 deletions

View File

@ -1,16 +1,19 @@
/* -------------------------------------------------------------------------
*
* worker_spi.c
* Sample background worker code that demonstrates usage of a database
* connection.
* Sample background worker code that demonstrates various coding
* patterns: establishing a database connection; starting and committing
* transactions; using GUC variables, and heeding SIGHUP to reread
* the configuration file; reporting to pg_stat_activity; using the
* process latch to sleep and exit in case of postmaster death.
*
* This code connects to a database, create a schema and table, and summarizes
* This code connects to a database, creates 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
* Copyright (C) 2013, PostgreSQL Global Development Group
*
* IDENTIFICATION
* contrib/worker_spi/worker_spi.c
@ -33,14 +36,22 @@
#include "executor/spi.h"
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "pgstat.h"
#include "utils/builtins.h"
#include "utils/snapmgr.h"
#include "tcop/utility.h"
PG_MODULE_MAGIC;
void _PG_init(void);
static bool got_sigterm = false;
/* flags set by signal handlers */
static volatile sig_atomic_t got_sighup = false;
static volatile sig_atomic_t got_sigterm = false;
/* GUC variables */
static int worker_spi_naptime = 10;
static int worker_spi_total_workers = 2;
typedef struct worktable
@ -49,6 +60,11 @@ typedef struct worktable
const char *name;
} worktable;
/*
* Signal handler for SIGTERM
* Set a flag to let the main loop to terminate, and set our latch to wake
* it up.
*/
static void
worker_spi_sigterm(SIGNAL_ARGS)
{
@ -61,14 +77,23 @@ worker_spi_sigterm(SIGNAL_ARGS)
errno = save_errno;
}
/*
* Signal handler for SIGHUP
* Set a flag to let the main loop to reread the config file, and set
* our latch to wake it up.
*/
static void
worker_spi_sighup(SIGNAL_ARGS)
{
elog(LOG, "got sighup!");
got_sighup = true;
if (MyProc)
SetLatch(&MyProc->procLatch);
}
/*
* Initialize workspace for a worker process: create the schema if it doesn't
* already exist.
*/
static void
initialize_worker_spi(worktable *table)
{
@ -77,10 +102,13 @@ initialize_worker_spi(worktable *table)
bool isnull;
StringInfoData buf;
SetCurrentStatementStartTimestamp();
StartTransactionCommand();
SPI_connect();
PushActiveSnapshot(GetTransactionSnapshot());
pgstat_report_activity(STATE_RUNNING, "initializing spi_worker schema");
/* XXX could we use CREATE SCHEMA IF NOT EXISTS? */
initStringInfo(&buf);
appendStringInfo(&buf, "select count(*) from pg_namespace where nspname = '%s'",
table->schema);
@ -110,6 +138,9 @@ initialize_worker_spi(worktable *table)
"WHERE type = 'total'",
table->schema, table->name, table->name, table->name);
/* set statement start time */
SetCurrentStatementStartTimestamp();
ret = SPI_execute(buf.data, false, 0);
if (ret != SPI_OK_UTILITY)
@ -119,6 +150,7 @@ initialize_worker_spi(worktable *table)
SPI_finish();
PopActiveSnapshot();
CommitTransactionCommand();
pgstat_report_activity(STATE_IDLE, NULL);
}
static void
@ -163,6 +195,9 @@ worker_spi_main(void *main_arg)
table->name,
table->name);
/*
* Main loop: do this until the SIGTERM handler tells us to terminate
*/
while (!got_sigterm)
{
int ret;
@ -176,17 +211,45 @@ worker_spi_main(void *main_arg)
*/
rc = WaitLatch(&MyProc->procLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
1000L);
worker_spi_naptime * 1000L);
ResetLatch(&MyProc->procLatch);
/* emergency bailout if postmaster has died */
if (rc & WL_POSTMASTER_DEATH)
proc_exit(1);
/*
* In case of a SIGHUP, just reload the configuration.
*/
if (got_sighup)
{
got_sighup = false;
ProcessConfigFile(PGC_SIGHUP);
}
/*
* Start a transaction on which we can run queries. Note that each
* StartTransactionCommand() call should be preceded by a
* SetCurrentStatementStartTimestamp() call, which sets both the time
* for the statement we're about the run, and also the transaction
* start time. Also, each other query sent to SPI should probably be
* preceded by SetCurrentStatementStartTimestamp(), so that statement
* start time is always up to date.
*
* The SPI_connect() call lets us run queries through the SPI manager,
* and the PushActiveSnapshot() call creates an "active" snapshot which
* is necessary for queries to have MVCC data to work on.
*
* The pgstat_report_activity() call makes our activity visible through
* the pgstat views.
*/
SetCurrentStatementStartTimestamp();
StartTransactionCommand();
SPI_connect();
PushActiveSnapshot(GetTransactionSnapshot());
pgstat_report_activity(STATE_RUNNING, buf.data);
/* We can now execute queries via SPI */
ret = SPI_execute(buf.data, false, 0);
if (ret != SPI_OK_UPDATE_RETURNING)
@ -207,9 +270,13 @@ worker_spi_main(void *main_arg)
table->schema, table->name, val);
}
/*
* And finish our transaction.
*/
SPI_finish();
PopActiveSnapshot();
CommitTransactionCommand();
pgstat_report_activity(STATE_IDLE, NULL);
}
proc_exit(0);
@ -218,46 +285,66 @@ worker_spi_main(void *main_arg)
/*
* Entrypoint of this module.
*
* We register two worker processes here, to demonstrate how that can be done.
* We register more than one worker process here, to demonstrate how that can
* be done.
*/
void
_PG_init(void)
{
BackgroundWorker worker;
worktable *table;
unsigned int i;
char name[20];
/* register the worker processes. These values are common for both */
/* get the configuration */
DefineCustomIntVariable("worker_spi.naptime",
"Duration between each check (in seconds).",
NULL,
&worker_spi_naptime,
10,
1,
INT_MAX,
PGC_SIGHUP,
0,
NULL,
NULL,
NULL);
DefineCustomIntVariable("worker_spi.total_workers",
"Number of workers.",
NULL,
&worker_spi_total_workers,
2,
1,
100,
PGC_POSTMASTER,
0,
NULL,
NULL,
NULL);
/* set up common data for all our workers */
worker.bgw_flags = BGWORKER_SHMEM_ACCESS |
BGWORKER_BACKEND_DATABASE_CONNECTION;
worker.bgw_start_time = BgWorkerStart_RecoveryFinished;
worker.bgw_restart_time = BGW_NEVER_RESTART;
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.
* Now fill in worker-specific data, and do the actual registrations.
*/
table = palloc(sizeof(worktable));
table->schema = pstrdup("schema1");
table->name = pstrdup("counted");
for (i = 1; i <= worker_spi_total_workers; i++)
{
sprintf(name, "worker %d", i);
worker.bgw_name = pstrdup(name);
worker.bgw_name = "SPI worker 1";
worker.bgw_restart_time = BGW_NEVER_RESTART;
worker.bgw_main_arg = (void *) table;
RegisterBackgroundWorker(&worker);
table = palloc(sizeof(worktable));
sprintf(name, "schema%d", i);
table->schema = pstrdup(name);
table->name = pstrdup("counted");
worker.bgw_main_arg = (void *) table;
/* 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);
RegisterBackgroundWorker(&worker);
}
}