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