diff --git a/contrib/pingpong/Makefile b/contrib/pingpong/Makefile new file mode 100644 index 0000000..c3a443c --- /dev/null +++ b/contrib/pingpong/Makefile @@ -0,0 +1,17 @@ +# contrib/pingpong/Makefile + +MODULES = pingpong + +EXTENSION = pingpong +DATA = pingpong--1.0.sql + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pingpong +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pingpong/pingpong--1.0.sql b/contrib/pingpong/pingpong--1.0.sql new file mode 100644 index 0000000..75c2982 --- /dev/null +++ b/contrib/pingpong/pingpong--1.0.sql @@ -0,0 +1,7 @@ +/* contrib/pingpong/pingpong--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pingpong" to load this file. \quit + +CREATE FUNCTION pingpong(pg_catalog.int8) RETURNS pg_catalog.void STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/contrib/pingpong/pingpong.c b/contrib/pingpong/pingpong.c new file mode 100644 index 0000000..597333f --- /dev/null +++ b/contrib/pingpong/pingpong.c @@ -0,0 +1,165 @@ +/* ------------------------------------------------------------------------- + * + * pingpong.c + * Demonstration code that plays ping-pong with process latches. + * + * Copyright (C) 2013, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pingpong/pingpong.c + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "fmgr.h" +#include "miscadmin.h" +#include "postmaster/bgworker.h" +#include "storage/barrier.h" +#include "storage/ipc.h" +#include "storage/latch.h" +#include "storage/lwlock.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/shmem.h" + +PG_MODULE_MAGIC; +PG_FUNCTION_INFO_V1(pingpong); + +void _PG_init(void); +void pingpong_main(Datum); +Datum pingpong(PG_FUNCTION_ARGS); + +/* flags set by signal handlers */ +static volatile sig_atomic_t got_sigterm = false; + +/* + * Signal handler for SIGTERM + * Set a flag to let the main loop to terminate, and set our latch to wake + * it up. + */ +static void +pingpong_sigterm(SIGNAL_ARGS) +{ + int save_errno = errno; + + got_sigterm = true; + if (MyProc) + SetLatch(&MyProc->procLatch); + + errno = save_errno; +} + +void +pingpong_main(Datum main_arg) +{ + PGPROC *proc; + + /* Establish signal handlers before unblocking signals. */ + pqsignal(SIGTERM, pingpong_sigterm); + + /* We're now ready to receive signals */ + BackgroundWorkerUnblockSignals(); + + /* + * We need to do this in order to get our PGPROC to show up in the + * ProcArray, so that the master can get hold of it. XXX. Is there a + * better way of doing this? + */ + InitProcessPhase2(); + + /* Find parent's PGPROC structure. */ + proc = BackendPidGetProc(MyBgworkerEntry->bgw_notify_pid); + + /* Play ping-pong until we're killed. */ + while (!got_sigterm) + { + ResetLatch(&MyProc->procLatch); + pg_memory_barrier(); + SetLatch(&proc->procLatch); + pg_memory_barrier(); + WaitLatch(&MyProc->procLatch, WL_LATCH_SET, 0); + } + + proc_exit(1); +} + +/* + * Dynamically launch an SPI worker. + */ +Datum +pingpong(PG_FUNCTION_ARGS) +{ + int64 count = PG_GETARG_INT64(0); + BackgroundWorker worker; + BackgroundWorkerHandle *handle; + BgwHandleStatus status; + pid_t pid; + PGPROC *proc; + + worker.bgw_flags = BGWORKER_SHMEM_ACCESS + | BGWORKER_EPHEMERAL | BGWORKER_PRECIOUS; + worker.bgw_start_time = BgWorkerStart_RecoveryFinished; + worker.bgw_restart_time = BGW_NEVER_RESTART; + worker.bgw_main = NULL; /* new worker might not have library loaded */ + sprintf(worker.bgw_library_name, "pingpong"); + sprintf(worker.bgw_function_name, "pingpong_main"); + snprintf(worker.bgw_name, BGW_MAXLEN, "pingpong"); + worker.bgw_main_arg = (Datum) 0; + /* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */ + worker.bgw_notify_pid = MyProcPid; + + /* Register background worker. */ + if (!RegisterDynamicBackgroundWorker(&worker, &handle)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("could not register background process"), + errhint("You may need to increase max_worker_processes."))); + + /* Wait until it starts so we can get the PID. */ + status = WaitForBackgroundWorkerStartup(handle, &pid); + + if (status == BGWH_STOPPED) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("could not start background process"), + errhint("More details may be available in the server log."))); + if (status == BGWH_POSTMASTER_DIED) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("cannot start background processes without postmaster"), + errhint("Kill all remaining database processes and restart the database."))); + Assert(status == BGWH_STARTED); + + /* + * Get the PGPROC for the background process. Unfortunately, we don't + * actually know that the client has gotten as far as acquiring a PGPROC; + * we only know that the postmaster managed to fork (or exec it). But + * the child will set our latch when it starts up, so we can just wait on + * our latch. + */ + while ((proc = BackendPidGetProc(pid)) == NULL) + { + WaitLatch(&MyProc->procLatch, WL_LATCH_SET, 0); + pg_memory_barrier(); + CHECK_FOR_INTERRUPTS(); + ResetLatch(&MyProc->procLatch); + } + + /* OK, we're ready to really do it. */ + while (count > 0) + { + ResetLatch(&MyProc->procLatch); + pg_memory_barrier(); + SetLatch(&proc->procLatch); + pg_memory_barrier(); + WaitLatch(&MyProc->procLatch, WL_LATCH_SET, 0); + CHECK_FOR_INTERRUPTS(); + --count; + } + + /* We must clean up the background worker before returning. */ + TerminateBackgroundWorker(handle); + + PG_RETURN_VOID(); +} diff --git a/contrib/pingpong/pingpong.control b/contrib/pingpong/pingpong.control new file mode 100644 index 0000000..f8aa447 --- /dev/null +++ b/contrib/pingpong/pingpong.control @@ -0,0 +1,5 @@ +# pingpong extension +comment = 'Play ping-pong with latches' +default_version = '1.0' +module_pathname = '$libdir/pingpong' +relocatable = true