On Sat, Aug 8, 2015 at 7:46 PM, Robert Haas <robertmh...@gmail.com> wrote:

> On Wed, Aug 5, 2015 at 3:33 AM, Ashutosh Bapat
> <ashutosh.ba...@enterprisedb.com> wrote:
> > This idea looks good.
>
> Thanks.  It needs testing though to see if it really works as
> intended.  Can you look into that?
>

PFA the patch containing your code changes + test module. See if that meets
your expectations.


>
> > Looking at larger picture, we should also enable this feature to be used
> by
> > auxilliary processes. It's very hard to add a new auxilliary process in
> > current code. One has to go add code at many places to make sure that the
> > auxilliary processes die and are re-started correctly. Even tougher to
> add a
> > parent auxilliary process, which spawns multiple worker processes.That
> would
> > be whole lot simpler if we could allow the auxilliary processes to use
> > background worker infrastructure (which is what they are utlimately).
>
> That's a separate patch, but, sure, we could do that.  I agree with
> Alvaro's comments: the postmaster should start all children.  Other
> processes should just request that it do so.  We have two mechanisms
> for that right now: the one used by bgworkers, and the one used by the
> AV launcher.
>

BY children I really meant workers that it requests postmaster to start,
not the OS definition of child.


>
> > BGWORKER_SHMEM_ACCESS has similar usage, except that it resets the on
> exit
> > callbacks and detaches the shared memory segment from the background
> worker.
> > That avoids a full cluster restart when one of those worker which can not
> > corrupt shared memory dies. But I do not see any check to prevent such
> > backend from calling PGSharedMemoryReattach()
>
> There isn't, but you shouldn't do that.  :-)
>
> This is C code; you can't protect against actively malicious code.
>

We have taken pains to check whether the worker was started with
BGWORKER_BACKEND_DATABASE_CONNECTION flag, when it requests to connect to a
database. I think it makes sense to do that with ACCESS_SHMEM flag as well.
Otherwise, some buggy extension would connect to the shared memory and exit
without postmaster restarting all the backends. Obvious one can argue that,
memory corruption is possible even without this flag, but we should try to
protect exposed interfaces.

>
> > So it looks like, it suffices to assume that background worker either
> needs
> > to access shared memory or doesn't. Any background worker having shared
> > memory access can also access database and thus becomes part of the
> backend
> > list. Or may be we just avoid these flags and treat every background
> worker
> > as if it passed both these flags. That will simplify a lot of code.
>
> I think it's useful to support workers that don't have shared memory
> access at all, because those can crash without causing a system-wide
> reset.  But I don't see the point in distinguishing between workers
> with shared-memory access and those with a database connection.  I
> mean, obviously the worker needs to be able to initialize itself
> either way, but there seems to be no reason to force that to be
> signalled in bgw_flags.  It can just depend on whether
> BackgroundWorkerInitializeConnection gets called.
>

+1.
-- 
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 000524d..1818f7c 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -148,22 +148,21 @@
  * 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 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, but we know
  * they will never become live backends.  dead_end children are not assigned a
  * PMChildSlot.
  *
- * Background workers that request shared memory access during registration are
- * in this list, too.
+ * Background workers are in this list, too.
  */
 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 */
 
 	/*
 	 * Flavor of backend or auxiliary process.  Note that BACKEND_TYPE_WALSND
 	 * backends initially announce themselves as BACKEND_TYPE_NORMAL, so if
@@ -397,27 +396,25 @@ static int	ServerLoop(void);
 static int	BackendStartup(Port *port);
 static int	ProcessStartupPacket(Port *port, bool SSLdone);
 static void processCancelRequest(Port *port, void *pkt);
 static int	initMasks(fd_set *rmask);
 static void report_fork_failure_to_client(Port *port, int errnum);
 static CAC_state canAcceptConnections(void);
 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);
 static void TerminateChildren(int signal);
 
 #define SignalChildren(sig)			   SignalSomeChildren(sig, BACKEND_TYPE_ALL)
 
 static int	CountChildren(int target);
-static int	CountUnconnectedWorkers(void);
 static void maybe_start_bgworker(void);
 static bool CreateOptsFile(int argc, char *argv[], char *fullprogname);
 static pid_t StartChildProcess(AuxProcType type);
 static void StartAutovacuumWorker(void);
 static void InitPostmasterDeathWatchHandle(void);
 
 /*
  * Archiver is allowed to start up at the current postmaster state?
  *
  * If WAL archiving is enabled always, we are allowed to start archiver
@@ -2407,21 +2404,20 @@ SIGHUP_handler(SIGNAL_ARGS)
 	int			save_errno = errno;
 
 	PG_SETMASK(&BlockSig);
 
 	if (Shutdown <= SmartShutdown)
 	{
 		ereport(LOG,
 				(errmsg("received SIGHUP, reloading configuration files")));
 		ProcessConfigFile(PGC_SIGHUP);
 		SignalChildren(SIGHUP);
-		SignalUnconnectedWorkers(SIGHUP);
 		if (StartupPID != 0)
 			signal_child(StartupPID, SIGHUP);
 		if (BgWriterPID != 0)
 			signal_child(BgWriterPID, SIGHUP);
 		if (CheckpointerPID != 0)
 			signal_child(CheckpointerPID, SIGHUP);
 		if (WalWriterPID != 0)
 			signal_child(WalWriterPID, SIGHUP);
 		if (WalReceiverPID != 0)
 			signal_child(WalReceiverPID, SIGHUP);
@@ -2484,21 +2480,20 @@ pmdie(SIGNAL_ARGS)
 			ereport(LOG,
 					(errmsg("received smart shutdown request")));
 
 			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);
 				/* and the bgwriter too */
 				if (BgWriterPID != 0)
 					signal_child(BgWriterPID, SIGTERM);
 				/* and the walwriter too */
 				if (WalWriterPID != 0)
 					signal_child(WalWriterPID, SIGTERM);
 
@@ -2536,25 +2531,25 @@ pmdie(SIGNAL_ARGS)
 			Shutdown = FastShutdown;
 			ereport(LOG,
 					(errmsg("received fast shutdown request")));
 
 			if (StartupPID != 0)
 				signal_child(StartupPID, SIGTERM);
 			if (BgWriterPID != 0)
 				signal_child(BgWriterPID, SIGTERM);
 			if (WalReceiverPID != 0)
 				signal_child(WalReceiverPID, SIGTERM);
-			SignalUnconnectedWorkers(SIGTERM);
 			if (pmState == PM_RECOVERY)
 			{
+				SignalSomeChildren(SIGTERM, BACKEND_TYPE_BGWORKER);
 				/*
-				 * Only startup, bgwriter, walreceiver, unconnected bgworkers,
+				 * Only startup, bgwriter, walreceiver, possibly bgworkers,
 				 * and/or 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;
 			}
 			else if (pmState == PM_RUN ||
 					 pmState == PM_WAIT_BACKUP ||
 					 pmState == PM_WAIT_READONLY ||
 					 pmState == PM_WAIT_BACKENDS ||
@@ -2992,39 +2987,35 @@ CleanupBackgroundWorker(int pid,
 		 * if it is in fact connected.
 		 */
 		if (!ReleasePostmasterChildSlot(rw->rw_child_slot) &&
 			(rw->rw_worker.bgw_flags & BGWORKER_SHMEM_ACCESS) != 0)
 		{
 			HandleChildCrash(pid, exitstatus, namebuf);
 			return true;
 		}
 
 		/* Get it out of the BackendList and clear out remaining data */
-		if (rw->rw_backend)
-		{
-			Assert(rw->rw_worker.bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION);
-			dlist_delete(&rw->rw_backend->elem);
+		dlist_delete(&rw->rw_backend->elem);
 #ifdef EXEC_BACKEND
-			ShmemBackendArrayRemove(rw->rw_backend);
+		ShmemBackendArrayRemove(rw->rw_backend);
 #endif
 
-			/*
-			 * It's possible that this background worker started some OTHER
-			 * background worker and asked to be notified when that worker
-			 * started or stopped.  If so, cancel any notifications destined
-			 * for the now-dead backend.
-			 */
-			if (rw->rw_backend->bgworker_notify)
-				BackgroundWorkerStopNotifications(rw->rw_pid);
-			free(rw->rw_backend);
-			rw->rw_backend = NULL;
-		}
+		/*
+		 * It's possible that this background worker started some OTHER
+		 * background worker and asked to be notified when that worker
+		 * started or stopped.  If so, cancel any notifications destined
+		 * for the now-dead backend.
+		 */
+		if (rw->rw_backend->bgworker_notify)
+			BackgroundWorkerStopNotifications(rw->rw_pid);
+		free(rw->rw_backend);
+		rw->rw_backend = NULL;
 		rw->rw_pid = 0;
 		rw->rw_child_slot = 0;
 		ReportBackgroundWorkerPID(rw);	/* report child death */
 
 		LogChildExit(EXIT_STATUS_0(exitstatus) ? DEBUG1 : LOG,
 					 namebuf, pid, exitstatus);
 
 		return true;
 	}
 
@@ -3153,29 +3144,26 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
 
 		rw = slist_container(RegisteredBgWorker, rw_lnode, siter.cur);
 		if (rw->rw_pid == 0)
 			continue;			/* not running */
 		if (rw->rw_pid == pid)
 		{
 			/*
 			 * Found entry for freshly-dead worker, so remove it.
 			 */
 			(void) ReleasePostmasterChildSlot(rw->rw_child_slot);
-			if (rw->rw_backend)
-			{
-				dlist_delete(&rw->rw_backend->elem);
+			dlist_delete(&rw->rw_backend->elem);
 #ifdef EXEC_BACKEND
-				ShmemBackendArrayRemove(rw->rw_backend);
+			ShmemBackendArrayRemove(rw->rw_backend);
 #endif
-				free(rw->rw_backend);
-				rw->rw_backend = NULL;
-			}
+			free(rw->rw_backend);
+			rw->rw_backend = NULL;
 			rw->rw_pid = 0;
 			rw->rw_child_slot = 0;
 			/* don't reset crashed_at */
 			/* don't report child stop, either */
 			/* Keep looping so we can signal remaining workers */
 		}
 		else
 		{
 			/*
 			 * This worker is still alive.  Unless we did so already, tell it
@@ -3498,21 +3486,20 @@ PostmasterStateMachine(void)
 		 * ones), and no walwriter, autovac launcher or bgwriter.  If we are
 		 * doing crash recovery or an immediate shutdown then we expect the
 		 * checkpointer to exit as well, otherwise not. The archiver, stats,
 		 * and syslogger processes are disregarded since they are not
 		 * connected to shared memory; we also disregard dead_end children
 		 * here. Walsenders are also disregarded, they will be terminated
 		 * 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 &&
 			(CheckpointerPID == 0 ||
 			 (!FatalError && Shutdown < ImmediateShutdown)) &&
 			WalWriterPID == 0 &&
 			AutoVacPID == 0)
 		{
 			if (Shutdown >= ImmediateShutdown || FatalError)
 			{
@@ -3721,53 +3708,20 @@ signal_child(pid_t pid, int signal)
 			if (kill(-pid, signal) < 0)
 				elog(DEBUG3, "kill(%ld,%d) failed: %m", (long) (-pid), signal);
 			break;
 		default:
 			break;
 	}
 #endif
 }
 
 /*
- * Send a signal to bgworkers that did not request backend connections
- *
- * The reason this is interesting is that workers that did request connections
- * are considered by SignalChildren; this function complements that one.
- */
-static bool
-SignalUnconnectedWorkers(int signal)
-{
-	slist_iter	iter;
-	bool		signaled = false;
-
-	slist_foreach(iter, &BackgroundWorkerList)
-	{
-		RegisteredBgWorker *rw;
-
-		rw = slist_container(RegisteredBgWorker, rw_lnode, iter.cur);
-
-		if (rw->rw_pid == 0)
-			continue;
-		/* ignore connected workers */
-		if (rw->rw_backend != NULL)
-			continue;
-
-		ereport(DEBUG4,
-				(errmsg_internal("sending signal %d to process %d",
-								 signal, (int) rw->rw_pid)));
-		signal_child(rw->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).
  */
 static bool
 SignalSomeChildren(int signal, int target)
 {
 	dlist_iter	iter;
 	bool		signaled = false;
 
 	dlist_foreach(iter, &BackendList)
@@ -3825,21 +3779,20 @@ TerminateChildren(int signal)
 	if (WalWriterPID != 0)
 		signal_child(WalWriterPID, signal);
 	if (WalReceiverPID != 0)
 		signal_child(WalReceiverPID, signal);
 	if (AutoVacPID != 0)
 		signal_child(AutoVacPID, signal);
 	if (PgArchPID != 0)
 		signal_child(PgArchPID, signal);
 	if (PgStatPID != 0)
 		signal_child(PgStatPID, signal);
-	SignalUnconnectedWorkers(signal);
 }
 
 /*
  * BackendStartup -- start backend process
  *
  * returns: STATUS_ERROR if the fork failed, STATUS_OK otherwise.
  *
  * Note: if you change this code, also consider StartAutovacuumWorker.
  */
 static int
@@ -5087,47 +5040,20 @@ PostmasterRandom(void)
 		}
 		while (random_seed == 0);
 
 		srandom(random_seed);
 	}
 
 	return random();
 }
 
 /*
- * Count up number of worker processes that did not request backend connections
- * See SignalUnconnectedWorkers for why this is interesting.
- */
-static int
-CountUnconnectedWorkers(void)
-{
-	slist_iter	iter;
-	int			cnt = 0;
-
-	slist_foreach(iter, &BackgroundWorkerList)
-	{
-		RegisteredBgWorker *rw;
-
-		rw = slist_container(RegisteredBgWorker, rw_lnode, iter.cur);
-
-		if (rw->rw_pid == 0)
-			continue;
-		/* ignore connected workers */
-		if (rw->rw_backend != NULL)
-			continue;
-
-		cnt++;
-	}
-	return cnt;
-}
-
-/*
  * Count up number of child processes of specified types (dead_end chidren
  * are always excluded).
  */
 static int
 CountChildren(int target)
 {
 	dlist_iter	iter;
 	int			cnt = 0;
 
 	dlist_foreach(iter, &BackendList)
@@ -5513,22 +5439,21 @@ do_start_bgworker(RegisteredBgWorker *rw)
 			ClosePostmasterPorts(false);
 
 			/* Do NOT release postmaster's working memory context */
 
 			MyBgworkerEntry = &rw->rw_worker;
 			StartBackgroundWorker();
 			break;
 #endif
 		default:
 			rw->rw_pid = worker_pid;
-			if (rw->rw_backend)
-				rw->rw_backend->pid = rw->rw_pid;
+			rw->rw_backend->pid = rw->rw_pid;
 			ReportBackgroundWorkerPID(rw);
 	}
 }
 
 /*
  * Does the current postmaster state require starting a worker with the
  * specified start_time?
  */
 static bool
 bgworker_should_start_now(BgWorkerStartTime start_time)
@@ -5677,44 +5602,33 @@ maybe_start_bgworker(void)
 				continue;
 			}
 		}
 
 		if (bgworker_should_start_now(rw->rw_worker.bgw_start_time))
 		{
 			/* reset crash time before calling assign_backendlist_entry */
 			rw->rw_crashed_at = 0;
 
 			/*
-			 * If necessary, allocate and assign the Backend element.  Note we
+			 * 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->rw_worker.bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
-			{
-				if (!assign_backendlist_entry(rw))
-					return;
-			}
-			else
-				rw->rw_child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
+			if (!assign_backendlist_entry(rw))
+				return;
 
 			do_start_bgworker(rw);		/* sets rw->rw_pid */
 
-			if (rw->rw_backend)
-			{
-				dlist_push_head(&BackendList, &rw->rw_backend->elem);
+			dlist_push_head(&BackendList, &rw->rw_backend->elem);
 #ifdef EXEC_BACKEND
-				ShmemBackendArrayAdd(rw->rw_backend);
+			ShmemBackendArrayAdd(rw->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;
 		}
 	}
diff --git a/src/test/modules/test_bgwnotify/Makefile b/src/test/modules/test_bgwnotify/Makefile
new file mode 100644
index 0000000..6931c09
--- /dev/null
+++ b/src/test/modules/test_bgwnotify/Makefile
@@ -0,0 +1,21 @@
+# src/test/modules/test_bgwnotify/Makefile
+
+MODULE_big = test_bgwnotify
+OBJS = test_bgwnotify.o $(WIN32RES)
+PGFILEDESC = "test_bgwnotify - example use of background worker notification infrastructure"
+
+EXTENSION = test_bgwnotify
+DATA = test_bgwnotify--1.0.sql
+
+REGRESS = test_bgwnotify
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_bgwnotify
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_bgwnotify/expected/test_bgwnotify.out b/src/test/modules/test_bgwnotify/expected/test_bgwnotify.out
new file mode 100644
index 0000000..b7e1b65
--- /dev/null
+++ b/src/test/modules/test_bgwnotify/expected/test_bgwnotify.out
@@ -0,0 +1,11 @@
+CREATE EXTENSION test_bgwnotify;
+--
+-- We're checking that the operations complete without crashing or hanging and
+-- that none of their internal sanity tests fail.
+--
+SELECT test_bgwnotify();
+ test_bgwnotify 
+----------------
+ t
+(1 row)
+
diff --git a/src/test/modules/test_bgwnotify/sql/test_bgwnotify.sql b/src/test/modules/test_bgwnotify/sql/test_bgwnotify.sql
new file mode 100644
index 0000000..e448ef0
--- /dev/null
+++ b/src/test/modules/test_bgwnotify/sql/test_bgwnotify.sql
@@ -0,0 +1,7 @@
+CREATE EXTENSION test_bgwnotify;
+
+--
+-- We're checking that the operations complete without crashing or hanging and
+-- that none of their internal sanity tests fail.
+--
+SELECT test_bgwnotify();
diff --git a/src/test/modules/test_bgwnotify/test_bgwnotify--1.0.sql b/src/test/modules/test_bgwnotify/test_bgwnotify--1.0.sql
new file mode 100644
index 0000000..f3423bc
--- /dev/null
+++ b/src/test/modules/test_bgwnotify/test_bgwnotify--1.0.sql
@@ -0,0 +1,7 @@
+/* src/test/modules/test_bgwnotify/test_bgwnotify--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_bgwnotify" to load this file. \quit
+
+CREATE FUNCTION test_bgwnotify() RETURNS pg_catalog.bool STRICT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_bgwnotify/test_bgwnotify.c b/src/test/modules/test_bgwnotify/test_bgwnotify.c
new file mode 100644
index 0000000..8eb5ace
--- /dev/null
+++ b/src/test/modules/test_bgwnotify/test_bgwnotify.c
@@ -0,0 +1,236 @@
+/* -------------------------------------------------------------------------
+ *
+ * test_bgwnotify.c
+ * Test for background worker notify feature. The SQL script for the test calls
+ * test_bgwnotify() function. This function in turn runs launcher background
+ * workers in different configurations. The launcher in turn runs multiple
+ * background workers, which do nothing but exit after sleeping for a while. The
+ * death of these workers is notified to the launcher. The launcher checks if
+ * deaths of all the workers are notified to it in various configurations. It
+ * throws error if notifications do not come as expected.
+ *
+ * Copyright (C) 2013-2014, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_bgwnotify/test_bgwnotify.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 "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_bgwnotify);
+void test_bgwnotify_launcher_main(Datum datum);
+void test_bgwnotify_worker_main(Datum datum);
+
+/* flags set by signal handlers */
+static volatile sig_atomic_t got_sigterm = false;
+static volatile sig_atomic_t got_sigusr1 = false;
+
+/* Worker sleep time in seconds */
+static int	worker_sleeptime = 5;
+
+/*
+ * Signal handler for SIGTERM
+ *		Set a flag to let the main loop to terminate, and set our latch to wake
+ *		it up.
+ */
+static void
+test_bgwnotify_sigterm(SIGNAL_ARGS)
+{
+	int			save_errno = errno;
+
+	got_sigterm = true;
+	SetLatch(MyLatch);
+
+	errno = save_errno;
+}
+
+/*
+ * Signal handler for SIGUSR1
+ *		Set a flag to tell the main loop to check status of worker processes,
+ *		and set our latch to wake it up.
+ */
+static void
+test_bgwnotify_sigusr1(SIGNAL_ARGS)
+{
+	int			save_errno = errno;
+
+	got_sigusr1 = true;
+	SetLatch(MyLatch);
+
+	errno = save_errno;
+}
+
+/*
+ * This function runs the launcher background worker in different
+ * configurations. The function raises an error if the launcher background
+ * worker doesn't exit within stipulated time.
+ */
+void
+test_bgwnotify_launcher_main(Datum main_arg)
+{
+	BackgroundWorker 		worker;
+	BackgroundWorkerHandle	*handle;
+	BgwHandleStatus			status;
+	int						pid;
+
+	/* Establish signal handlers before unblocking signals. */
+	pqsignal(SIGTERM, test_bgwnotify_sigterm);
+	pqsignal(SIGUSR1, test_bgwnotify_sigusr1);
+
+	/* We're now ready to receive signals */
+	BackgroundWorkerUnblockSignals();
+
+	/* Register one background worker */
+	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 = NULL;		/* new worker might not have library loaded */
+	sprintf(worker.bgw_library_name, "test_bgwnotify");
+	sprintf(worker.bgw_function_name, "test_bgwnotify_worker_main");
+	worker.bgw_notify_pid = MyProcPid;
+	snprintf(worker.bgw_name, BGW_MAXLEN, "test_bgwnotify_worker");
+	worker.bgw_main_arg = 0;
+
+	RegisterDynamicBackgroundWorker(&worker, &handle);
+
+	status = WaitForBackgroundWorkerStartup(handle, &pid);
+
+	if (status != BGWH_STARTED)
+	{
+		elog(WARNING, "could not start background worker (status = %d), exiting.", status);
+		proc_exit(1);
+	}
+
+	status = WaitForBackgroundWorkerShutdown(handle);
+
+	if (status == BGWH_STOPPED)
+		proc_exit(0);
+
+	/*
+	 * Perform an unclean exit, so that the postmaster restarts everything. That
+	 * way the backend which initiated this test gets killed and frontend gets
+	 * an error message.
+	 */
+	exit(1);
+}
+
+/*
+ * This function runs the background worker. It sleeps for worker_sleeptime and
+ * quits.
+ */
+void
+test_bgwnotify_worker_main(Datum main_arg)
+{
+	int			rc;
+
+	/* We're now ready to receive signals */
+	BackgroundWorkerUnblockSignals();
+
+	ResetLatch(MyLatch);
+	/*
+	 * The worker registered will wait for worker_sleeptime and is expected to
+	 * quit then. The launcher is expected to get a notification when the worker
+	 * quits. Let the launcher wait for twice the time the worker waits. Raise
+	 * error if the launcher doesn't receive the noitification. 
+	 */
+	rc = WaitLatch(MyLatch,
+				   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+				   worker_sleeptime * 1000L);
+
+	/*
+	 * Only timeout should wake this worker up. Everything else is error
+	 * condition.
+	 */
+	if (rc & WL_TIMEOUT)
+		proc_exit(0);
+
+	/*
+	 * Perform an unclean exit, so that the postmaster restarts everything. That
+	 * way the backend which initiated this test gets killed and frontend gets
+	 * an error message.
+	 */
+	exit(1);
+}
+
+/*
+ * Dynamically launch an SPI worker.
+ */
+Datum
+test_bgwnotify(PG_FUNCTION_ARGS)
+{
+	BackgroundWorker worker;
+	BackgroundWorkerHandle *handle;
+	BgwHandleStatus status;
+	pid_t		pid;
+
+	/* test1: shared memory and database access */
+	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 = NULL;		/* new worker might not have library loaded */
+	sprintf(worker.bgw_library_name, "test_bgwnotify");
+	sprintf(worker.bgw_function_name, "test_bgwnotify_launcher_main");
+	snprintf(worker.bgw_name, BGW_MAXLEN, "test_bgwnotify_launcher");
+	worker.bgw_main_arg = 0; 
+	/* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
+	worker.bgw_notify_pid = MyProcPid;
+
+	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
+		PG_RETURN_BOOL(false);
+
+	status = WaitForBackgroundWorkerStartup(handle, &pid);
+
+	if (status != BGWH_STARTED)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+				 errmsg("could not start background process"),
+			   errhint("More details may be available in the server log.")));
+
+	/* Wait for the launcher to shut down */
+	WaitForBackgroundWorkerShutdown(handle);
+
+	/* test2: shared memory access */
+	worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
+	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, "test_bgwnotify");
+	sprintf(worker.bgw_function_name, "test_bgwnotify_launcher_main");
+	snprintf(worker.bgw_name, BGW_MAXLEN, "test_bgwnotify_launcher");
+	worker.bgw_main_arg = 0; 
+	/* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
+	worker.bgw_notify_pid = MyProcPid;
+
+	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
+		PG_RETURN_BOOL(false);
+
+	status = WaitForBackgroundWorkerStartup(handle, &pid);
+
+	if (status != BGWH_STARTED)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+				 errmsg("could not start background process"),
+			   errhint("More details may be available in the server log.")));
+
+	/* Wait for the launcher to shut down */
+	WaitForBackgroundWorkerShutdown(handle);
+
+	PG_RETURN_BOOL(true);
+}
diff --git a/src/test/modules/test_bgwnotify/test_bgwnotify.control b/src/test/modules/test_bgwnotify/test_bgwnotify.control
new file mode 100644
index 0000000..c2881fc
--- /dev/null
+++ b/src/test/modules/test_bgwnotify/test_bgwnotify.control
@@ -0,0 +1,4 @@
+comment = 'Test code for background worker notification'
+default_version = '1.0'
+module_pathname = '$libdir/test_bgwnotify'
+relocatable = true
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to