On Mon, Nov 27, 2017 at 10:25 PM, Thomas Munro <thomas.mu...@enterprisedb.com> wrote: > On Thu, Nov 23, 2017 at 12:36 AM, Thomas Munro > <thomas.mu...@enterprisedb.com> wrote: >> Here's a new patch set with responses to the last batch of review comments. > > Rebased on top of the recent SGML->XML change.
Andres asked me off-list how I tested the barrier.c case where a backend detaches, releasing other waiters. There are special cases in BarrierArriveAndWait() and BarrierDetach() for that to make sure that the phase advances and waiters are released if they were only waiting for this one backend to arrive, and that exactly one of them is "elected" for any serial work. Normally the last to arrive is elected (see earlier discussion about why that's a good idea), but if the one that would be last detaches instead of arriving then we'll have to elect someone else. Here is a throw-away test harness that can be used to exercise that case. CREATE EXTENSION test_barrier; SELECT test_barrier_detach_releases(1);. Adding a sleep before BarrierDetach can be used to influence the race, and adding elog messages to barrier.c can be used to see when the special path is taken. -- Thomas Munro http://www.enterprisedb.com
From 4ba3fe2e318dd1a7b54b3b9ae8853531ed3a6e12 Mon Sep 17 00:00:00 2001 From: Thomas Munro <thomas.mu...@enterprisedb.com> Date: Thu, 30 Nov 2017 14:00:10 +1300 Subject: [PATCH] A simple test module for barrier.c. Some things to try: CREATE EXTENSION test_barrier; SELECT test_barrier_detach_releases(1); SELECT test_barrier_reattach_random(4, 1000); SELECT test_barrier_alternate(4, 1000); --- src/test/modules/test_barrier/Makefile | 18 ++ .../modules/test_barrier/test_barrier--1.0.sql | 18 ++ src/test/modules/test_barrier/test_barrier.c | 270 +++++++++++++++++++++ src/test/modules/test_barrier/test_barrier.control | 4 + 4 files changed, 310 insertions(+) create mode 100644 src/test/modules/test_barrier/Makefile create mode 100644 src/test/modules/test_barrier/test_barrier--1.0.sql create mode 100644 src/test/modules/test_barrier/test_barrier.c create mode 100644 src/test/modules/test_barrier/test_barrier.control diff --git a/src/test/modules/test_barrier/Makefile b/src/test/modules/test_barrier/Makefile new file mode 100644 index 00000000000..9f330b6a45d --- /dev/null +++ b/src/test/modules/test_barrier/Makefile @@ -0,0 +1,18 @@ +# src/test/modules/test_barrier/Makefile + +MODULES = test_barrier + +EXTENSION = test_barrier +DATA = test_barrier--1.0.sql +PGFILEDESC = "test_barrier -- some tests for barrier.c" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_barrier +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_barrier/test_barrier--1.0.sql b/src/test/modules/test_barrier/test_barrier--1.0.sql new file mode 100644 index 00000000000..7077f3f6567 --- /dev/null +++ b/src/test/modules/test_barrier/test_barrier--1.0.sql @@ -0,0 +1,18 @@ +\echo Use "CREATE EXTENSION test_barrier" to load this file. \quit + +CREATE FUNCTION test_barrier_alternate(workers int, loops int) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION test_barrier_reattach_random(workers int, end_phase int) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION test_barrier_detach_releases(workers int) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C; + + diff --git a/src/test/modules/test_barrier/test_barrier.c b/src/test/modules/test_barrier/test_barrier.c new file mode 100644 index 00000000000..98adefa809d --- /dev/null +++ b/src/test/modules/test_barrier/test_barrier.c @@ -0,0 +1,270 @@ +#include "postgres.h" + +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/bgworker.h" +#include "storage/barrier.h" +#include "storage/dsm.h" +#include "storage/proc.h" +#include "utils/builtins.h" +#include "utils/resowner.h" + +#include <stdlib.h> +#include <unistd.h> + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(test_barrier_alternate); +PG_FUNCTION_INFO_V1(test_barrier_reattach_random); +PG_FUNCTION_INFO_V1(test_barrier_detach_releases); + +extern Datum test_barrier_main(Datum); + +typedef enum test_mode +{ + TEST_MODE_ALTERNATE, + TEST_MODE_REATTACH_RANDOM, + TEST_MODE_DETACH_RELEASES +} test_mode; + +typedef struct test_barrier_alternate_state +{ + Barrier barrier1; + Barrier barrier2; + int loops; +} test_barrier_alternate_state; + +typedef struct test_barrier_reattach_random_state +{ + Barrier barrier; + int end_phase; +} test_barrier_reattach_random_state; + +typedef struct test_barrier_detach_releases_state +{ + Barrier barrier; +} test_barrier_detach_releases_state; + +typedef struct test_barrier_state +{ + test_mode mode; + union + { + test_barrier_alternate_state alternate_state; + test_barrier_reattach_random_state reattach_random_state; + test_barrier_detach_releases_state detach_releases_state; + }; +} test_barrier_state; + +/* + * Wait at barrier1 and then barrier2, state->loops times. + */ +static void +do_test_barrier_alternate(test_barrier_alternate_state * state) +{ + int i; + + for (i = 0; i < state->loops; ++i) + { + BarrierArriveAndWait(&state->barrier1, PG_WAIT_EXTENSION); + BarrierArriveAndWait(&state->barrier2, PG_WAIT_EXTENSION); + } +} + +/* + * Attach, wait a random numer of times, then detach, repeatedly until + * state->end_phase is reached. + */ +static void +do_test_barrier_reattach_random(test_barrier_reattach_random_state * state) +{ + bool done = false; + int expected_phase; + int i; + int nwaits; + + /* Make sure each backend uses a different pseudo-random sequence. */ + srand48(getpid()); + + while (!done) + { + expected_phase = BarrierAttach(&state->barrier); + nwaits = (int) (lrand48() % 8); + for (i = 0; i < nwaits; ++i) + { + if (expected_phase == state->end_phase) + { + done = true; + break; + } + + BarrierArriveAndWait(&state->barrier, PG_WAIT_EXTENSION); + ++expected_phase; + Assert(BarrierPhase(&state->barrier) == expected_phase); + } + BarrierDetach(&state->barrier); + } +} + +/* + * Wait twice. The first time gets us in sync with the leader + * process, and the second time we'll be released when the leader detaches. + * (In this case we're using a dynamic barrier, but the leader attached for + * the workers already which is a bit weird but necessary for this test.) + */ +static void +do_test_barrier_detach_releases(test_barrier_detach_releases_state *state) +{ + /* Make sure the leader is here. */ + BarrierArriveAndWait(&state->barrier, PG_WAIT_EXTENSION); + /* Wait for the leader to detach. */ + BarrierArriveAndWait(&state->barrier, PG_WAIT_EXTENSION); +} + +Datum +test_barrier_main(Datum arg) +{ + dsm_segment *segment; + test_barrier_state *state; + + BackgroundWorkerUnblockSignals(); + + CurrentResourceOwner = ResourceOwnerCreate(NULL, "test_barrier_main toplevel"); + + segment = dsm_attach(DatumGetInt32(arg)); + state = (test_barrier_state *) dsm_segment_address(segment); + switch (state->mode) + { + case TEST_MODE_ALTERNATE: + do_test_barrier_alternate(&state->alternate_state); + break; + case TEST_MODE_REATTACH_RANDOM: + do_test_barrier_reattach_random(&state->reattach_random_state); + break; + case TEST_MODE_DETACH_RELEASES: + do_test_barrier_detach_releases(&state->detach_releases_state); + break; + default: + Assert(0); + } + dsm_detach(segment); + + return (Datum) 0; +} + +static void +launch_test(test_mode mode, int workers, int n) +{ + BackgroundWorkerHandle **handles; + test_barrier_state *state; + dsm_segment *segment; + int i; + + handles = palloc(sizeof(BackgroundWorkerHandle *) * workers); + + segment = dsm_create(sizeof(test_barrier_state), 0); + state = (test_barrier_state *) dsm_segment_address(segment); + + /* Initialize state. */ + state->mode = mode; + switch (mode) + { + case TEST_MODE_ALTERNATE: + /* Initialize a static barrier for 'workers' workers. */ + state->alternate_state.loops = n; + BarrierInit(&state->alternate_state.barrier1, workers); + BarrierInit(&state->alternate_state.barrier2, workers); + break; + case TEST_MODE_REATTACH_RANDOM: + /* Initialize a dynamic barrier. They'll attach and detach. */ + state->reattach_random_state.end_phase = n; + BarrierInit(&state->reattach_random_state.barrier, 0); + break; + case TEST_MODE_DETACH_RELEASES: + /* Initialize a dynamic barrier. */ + BarrierInit(&state->detach_releases_state.barrier, 0); + /* Attach on behalf of all participants... */ + for (i = 0; i < 1 + workers; ++i) + BarrierAttach(&state->detach_releases_state.barrier); + break; + default: + Assert(0); + } + + /* Start workers. */ + for (i = 0; i < workers; ++i) + { + BackgroundWorker bgw; + + snprintf(bgw.bgw_name, sizeof(bgw.bgw_name), "worker%d", i); + bgw.bgw_flags = BGWORKER_SHMEM_ACCESS; + bgw.bgw_start_time = BgWorkerStart_ConsistentState; + bgw.bgw_restart_time = BGW_NEVER_RESTART; + snprintf(bgw.bgw_library_name, sizeof(bgw.bgw_library_name), + "test_barrier"); + snprintf(bgw.bgw_function_name, sizeof(bgw.bgw_function_name), + "test_barrier_main"); + bgw.bgw_main_arg = Int32GetDatum(dsm_segment_handle(segment)); + bgw.bgw_notify_pid = MyProcPid; + + if (!RegisterDynamicBackgroundWorker(&bgw, &handles[i])) + elog(ERROR, "Can't start worker"); + } + + /* Some tests require the leader to do something. */ + switch (mode) + { + case TEST_MODE_DETACH_RELEASES: + /* Workers are waiting for us... */ + BarrierArriveAndWait(&state->detach_releases_state.barrier, + PG_WAIT_EXTENSION); + /* + * Workers are still waiting for us, and we'll release them by + * detaching. + */ + BarrierDetach(&state->detach_releases_state.barrier); + break; + default: + ; + } + + /* Wait for workers to complete. */ + for (i = 0; i < workers; ++i) + WaitForBackgroundWorkerShutdown(handles[i]); + + dsm_detach(segment); +} + +Datum +test_barrier_reattach_random(PG_FUNCTION_ARGS) +{ + int workers = PG_GETARG_INT32(0); + int end_phase = PG_GETARG_INT32(1); + + launch_test(TEST_MODE_REATTACH_RANDOM, workers, end_phase); + + return (Datum) 0; +} + +Datum +test_barrier_alternate(PG_FUNCTION_ARGS) +{ + int workers = PG_GETARG_INT32(0); + int loops = PG_GETARG_INT32(1); + + launch_test(TEST_MODE_ALTERNATE, workers, loops); + + return (Datum) 0; +} + +Datum +test_barrier_detach_releases(PG_FUNCTION_ARGS) +{ + int workers = PG_GETARG_INT32(0); + + launch_test(TEST_MODE_DETACH_RELEASES, workers, 0); + + return (Datum) 0; +} diff --git a/src/test/modules/test_barrier/test_barrier.control b/src/test/modules/test_barrier/test_barrier.control new file mode 100644 index 00000000000..1cfe3bc8af3 --- /dev/null +++ b/src/test/modules/test_barrier/test_barrier.control @@ -0,0 +1,4 @@ +comment = 'test_barrier' +default_version = '1.0' +module_pathname = '$libdir/test_barrier' +relocatable = true -- 2.15.0