On Mon, Feb 20, 2023 at 5:52 PM Thomas Munro <thomas.mu...@gmail.com> wrote:
> I'm wondering about this bit in rebin_segment():
>
> +       if (segment_map->header == NULL)
> +               return;
>
> Why would we be rebinning an uninitialised/unused segment?

Answering my own question: because destroy_superblock() can do that.
So I think destroy_superblock() should test for that case, not
rebin_segment().  See attached.
From f0f808595145c0eabb7ccdcc5b8701798d5722d1 Mon Sep 17 00:00:00 2001
From: Liu Dongming <ldming...@gmail.com>
Date: Fri, 18 Mar 2022 11:49:06 +0800
Subject: [PATCH v4 1/2] Re-bin segment when memory pages are freed.

It's OK to be lazy about re-binning memory segments when allocating,
because that can only leave segments in a bin that's too high.  We'll
search higher bins if necessary while allocating next time, and
also eventually re-bin, so no memory can become unreachable that way.

However, when freeing memory, the largest contiguous range of free pages
might go up, so we should re-bin eagerly to make sure we don't leave the
segment in a bin that is too low for get_best_segment() to find.

The re-binning code is moved into a function of its own, so it can be
called whenever free pages are returned to the segment's free page map.

Back-patch to all supported releases.

Author: Dongming Liu <ldming...@gmail.com>
Reviewed-by: Robert Haas <robertmh...@gmail.com>
Reviewed-by: Thomas Munro <thomas.mu...@gmail.com>
Discussion: https://postgr.es/m/CAL1p7e8LzB2LSeAXo2pXCW4%2BRya9s0sJ3G_ReKOU%3DAjSUWjHWQ%40mail.gmail.com
---
 src/backend/utils/mmgr/dsa.c | 65 ++++++++++++++++++++++++------------
 1 file changed, 43 insertions(+), 22 deletions(-)

diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index 7a3781466e..8d1aace40a 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -418,6 +418,7 @@ static dsa_area *attach_internal(void *place, dsm_segment *segment,
 								 dsa_handle handle);
 static void check_for_freed_segments(dsa_area *area);
 static void check_for_freed_segments_locked(dsa_area *area);
+static void rebin_segment(dsa_area *area, dsa_segment_map *segment_map);
 
 /*
  * Create a new shared area in a new DSM segment.  Further DSM segments will
@@ -869,7 +870,11 @@ dsa_free(dsa_area *area, dsa_pointer dp)
 		FreePageManagerPut(segment_map->fpm,
 						   DSA_EXTRACT_OFFSET(span->start) / FPM_PAGE_SIZE,
 						   span->npages);
+
+		/* Move segment to appropriate bin if necessary. */
+		rebin_segment(area, segment_map);
 		LWLockRelease(DSA_AREA_LOCK(area));
+
 		/* Unlink span. */
 		LWLockAcquire(DSA_SCLASS_LOCK(area, DSA_SCLASS_SPAN_LARGE),
 					  LW_EXCLUSIVE);
@@ -1858,6 +1863,11 @@ destroy_superblock(dsa_area *area, dsa_pointer span_pointer)
 			segment_map->mapped_address = NULL;
 		}
 	}
+
+	/* Move segment to appropriate bin if necessary. */
+	if (segment_map->header != NULL)
+		rebin_segment(area, segment_map);
+
 	LWLockRelease(DSA_AREA_LOCK(area));
 
 	/*
@@ -2021,28 +2031,7 @@ get_best_segment(dsa_area *area, size_t npages)
 			/* Re-bin it if it's no longer in the appropriate bin. */
 			if (contiguous_pages < threshold)
 			{
-				size_t		new_bin;
-
-				new_bin = contiguous_pages_to_segment_bin(contiguous_pages);
-
-				/* Remove it from its current bin. */
-				unlink_segment(area, segment_map);
-
-				/* Push it onto the front of its new bin. */
-				segment_map->header->prev = DSA_SEGMENT_INDEX_NONE;
-				segment_map->header->next =
-					area->control->segment_bins[new_bin];
-				segment_map->header->bin = new_bin;
-				area->control->segment_bins[new_bin] = segment_index;
-				if (segment_map->header->next != DSA_SEGMENT_INDEX_NONE)
-				{
-					dsa_segment_map *next;
-
-					next = get_segment_by_index(area,
-												segment_map->header->next);
-					Assert(next->header->bin == new_bin);
-					next->header->prev = segment_index;
-				}
+				rebin_segment(area, segment_map);
 
 				/*
 				 * But fall through to see if it's enough to satisfy this
@@ -2297,3 +2286,35 @@ check_for_freed_segments_locked(dsa_area *area)
 		area->freed_segment_counter = freed_segment_counter;
 	}
 }
+
+/*
+ * Re-bin segment if it's no longer in the appropriate bin.
+ */
+static void
+rebin_segment(dsa_area *area, dsa_segment_map *segment_map)
+{
+	size_t		new_bin;
+	dsa_segment_index segment_index;
+
+	new_bin = contiguous_pages_to_segment_bin(fpm_largest(segment_map->fpm));
+	if (segment_map->header->bin == new_bin)
+		return;
+
+	/* Remove it from its current bin. */
+	unlink_segment(area, segment_map);
+
+	/* Push it onto the front of its new bin. */
+	segment_index = get_segment_index(area, segment_map);
+	segment_map->header->prev = DSA_SEGMENT_INDEX_NONE;
+	segment_map->header->next = area->control->segment_bins[new_bin];
+	segment_map->header->bin = new_bin;
+	area->control->segment_bins[new_bin] = segment_index;
+	if (segment_map->header->next != DSA_SEGMENT_INDEX_NONE)
+	{
+		dsa_segment_map *next;
+
+		next = get_segment_by_index(area, segment_map->header->next);
+		Assert(next->header->bin == new_bin);
+		next->header->prev = segment_index;
+	}
+}
-- 
2.39.2

From 65be852dc7d2377d42aaec64a5fe9b99472f5008 Mon Sep 17 00:00:00 2001
From: Liu Dongming <ldming...@gmail.com>
Date: Fri, 18 Mar 2022 13:26:11 +0800
Subject: [PATCH v4 2/2] Add a test module to exercise dsa.c.

Code originally developed along with dsa.c back in 2016, now extended
with a capped allocate-then-free-twice test by Dongming Liu, as part of
a bug investigation and fix.

Author: Dongming Liu <ldming...@gmail.com>
Author: Thomas Munro <thomas.mu...@gmail.com>
Discussion: https://postgr.es/m/CAL1p7e8LzB2LSeAXo2pXCW4%2BRya9s0sJ3G_ReKOU%3DAjSUWjHWQ%40mail.gmail.com
Discussion: https://postgr.es/m/CAEepm%3D3U7%2BRo7%3DECeQuAZoeFXs8iDVX56NXGCV7z3%3D%2BH%2BWd0Sw%40mail.gmail.com
---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/meson.build                  |   1 +
 src/test/modules/test_dsa/.gitignore          |   4 +
 src/test/modules/test_dsa/Makefile            |  19 +
 .../modules/test_dsa/expected/test_dsa.out    |  55 +++
 src/test/modules/test_dsa/meson.build         |  36 ++
 src/test/modules/test_dsa/sql/test_dsa.sql    |  13 +
 src/test/modules/test_dsa/test_dsa--1.0.sql   |  21 +
 src/test/modules/test_dsa/test_dsa.c          | 407 ++++++++++++++++++
 src/test/modules/test_dsa/test_dsa.control    |   5 +
 src/tools/pgindent/typedefs.list              |   4 +
 11 files changed, 566 insertions(+)
 create mode 100644 src/test/modules/test_dsa/.gitignore
 create mode 100644 src/test/modules/test_dsa/Makefile
 create mode 100644 src/test/modules/test_dsa/expected/test_dsa.out
 create mode 100644 src/test/modules/test_dsa/meson.build
 create mode 100644 src/test/modules/test_dsa/sql/test_dsa.sql
 create mode 100644 src/test/modules/test_dsa/test_dsa--1.0.sql
 create mode 100644 src/test/modules/test_dsa/test_dsa.c
 create mode 100644 src/test/modules/test_dsa/test_dsa.control

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 6331c976dc..23a70b7459 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
 		  test_copy_callbacks \
 		  test_custom_rmgrs \
 		  test_ddl_deparse \
+		  test_dsa \
 		  test_extensions \
 		  test_ginpostinglist \
 		  test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 17d369e378..d0f80c964d 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_bloomfilter')
 subdir('test_copy_callbacks')
 subdir('test_custom_rmgrs')
 subdir('test_ddl_deparse')
+subdir('test_dsa')
 subdir('test_extensions')
 subdir('test_ginpostinglist')
 subdir('test_integerset')
diff --git a/src/test/modules/test_dsa/.gitignore b/src/test/modules/test_dsa/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsa/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsa/Makefile b/src/test/modules/test_dsa/Makefile
new file mode 100644
index 0000000000..f45642a32a
--- /dev/null
+++ b/src/test/modules/test_dsa/Makefile
@@ -0,0 +1,19 @@
+# src/test/modules/test_dsa/Makefile
+
+MODULES = test_dsa
+
+EXTENSION = test_dsa
+DATA = test_dsa--1.0.sql
+PGFILEDESC = "test_dsa -- tests for DSA areas"
+REGRESS = test_dsa
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsa
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsa/expected/test_dsa.out b/src/test/modules/test_dsa/expected/test_dsa.out
new file mode 100644
index 0000000000..4d594130ca
--- /dev/null
+++ b/src/test/modules/test_dsa/expected/test_dsa.out
@@ -0,0 +1,55 @@
+CREATE EXTENSION test_dsa;
+SELECT test_dsa_random(3, 5, 16, 10240, 'random');
+ test_dsa_random 
+-----------------
+ 
+(1 row)
+
+SELECT test_dsa_random(3, 5, 16, 10240, 'forwards');
+ test_dsa_random 
+-----------------
+ 
+(1 row)
+
+SELECT test_dsa_random(3, 5, 16, 10240, 'backwards');
+ test_dsa_random 
+-----------------
+ 
+(1 row)
+
+SELECT count(*) from test_dsa_random_parallel(3, 5, 16, 10240, 'random', 5);
+ count 
+-------
+     5
+(1 row)
+
+SELECT count(*) from test_dsa_random_parallel(3, 5, 16, 10240, 'forwards', 5);
+ count 
+-------
+     5
+(1 row)
+
+SELECT count(*) from test_dsa_random_parallel(3, 5, 16, 10240, 'backwards', 5);
+ count 
+-------
+     5
+(1 row)
+
+SELECT test_dsa_oom(1024);
+ test_dsa_oom 
+--------------
+ 
+(1 row)
+
+SELECT test_dsa_oom(8192);
+ test_dsa_oom 
+--------------
+ 
+(1 row)
+
+SELECT test_dsa_oom(10240);
+ test_dsa_oom 
+--------------
+ 
+(1 row)
+
diff --git a/src/test/modules/test_dsa/meson.build b/src/test/modules/test_dsa/meson.build
new file mode 100644
index 0000000000..4578756ecf
--- /dev/null
+++ b/src/test/modules/test_dsa/meson.build
@@ -0,0 +1,36 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+# FIXME: prevent install during main install, but not during test :/
+
+test_dsa_sources = files(
+  'test_dsa.c',
+)
+
+if host_system == 'windows'
+  test_dsa_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_dsa',
+    '--FILEDESC', 'test_dsa - test code for dsa.c',])
+endif
+
+test_dsa = shared_module('test_dsa',
+  test_dsa_sources,
+  kwargs: pg_mod_args,
+)
+testprep_targets += test_dsa
+
+install_data(
+  'test_dsa.control',
+  'test_dsa--1.0.sql',
+  kwargs: contrib_data_args,
+)
+
+tests += {
+  'name': 'test_dsa',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'test_dsa',
+    ],
+  },
+}
diff --git a/src/test/modules/test_dsa/sql/test_dsa.sql b/src/test/modules/test_dsa/sql/test_dsa.sql
new file mode 100644
index 0000000000..3ce5abbeb5
--- /dev/null
+++ b/src/test/modules/test_dsa/sql/test_dsa.sql
@@ -0,0 +1,13 @@
+CREATE EXTENSION test_dsa;
+
+SELECT test_dsa_random(3, 5, 16, 10240, 'random');
+SELECT test_dsa_random(3, 5, 16, 10240, 'forwards');
+SELECT test_dsa_random(3, 5, 16, 10240, 'backwards');
+
+SELECT count(*) from test_dsa_random_parallel(3, 5, 16, 10240, 'random', 5);
+SELECT count(*) from test_dsa_random_parallel(3, 5, 16, 10240, 'forwards', 5);
+SELECT count(*) from test_dsa_random_parallel(3, 5, 16, 10240, 'backwards', 5);
+
+SELECT test_dsa_oom(1024);
+SELECT test_dsa_oom(8192);
+SELECT test_dsa_oom(10240);
\ No newline at end of file
diff --git a/src/test/modules/test_dsa/test_dsa--1.0.sql b/src/test/modules/test_dsa/test_dsa--1.0.sql
new file mode 100644
index 0000000000..ab575ff66e
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa--1.0.sql
@@ -0,0 +1,21 @@
+/* src/test/modules/test_dsa/test_dsa--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsa" to load this file. \quit
+
+CREATE FUNCTION test_dsa_random(loops int, num_allocs int, min_alloc int, max_alloc int, mode text)
+RETURNS VOID
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+CREATE TYPE test_dsa_row AS (pid int, allocations bigint, elapsed interval);
+
+CREATE FUNCTION test_dsa_random_parallel(loops int, num_allocs int, min_alloc int, max_alloc int, mode text, workers int)
+RETURNS SETOF test_dsa_row
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+CREATE FUNCTION test_dsa_oom(alloc_size int)
+RETURNS VOID
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
\ No newline at end of file
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
new file mode 100644
index 0000000000..071243a4db
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -0,0 +1,407 @@
+/* -------------------------------------------------------------------------
+ *
+ * test_dsa.c
+ *		Simple exercises for dsa.c.
+ *
+ * Copyright (C) 2016-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_dsa/test_dsa.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/bgworker.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "storage/proc.h"
+#include "utils/builtins.h"
+#include "utils/dsa.h"
+#include "utils/resowner.h"
+#include "utils/timestamp.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_dsa_random);
+PG_FUNCTION_INFO_V1(test_dsa_random_parallel);
+PG_FUNCTION_INFO_V1(test_dsa_oom);
+
+PGDLLEXPORT void test_dsa_random_worker_main(Datum arg);
+
+/* Which order to free objects in, within each loop. */
+typedef enum
+{
+	/* Free in random order. */
+	MODE_RANDOM,
+	/* Free in the same order we allocated (FIFO). */
+	MODE_FORWARDS,
+	/* Free in reverse order of allocation (LIFO). */
+	MODE_BACKWARDS
+} test_mode;
+
+/* Per-worker results. */
+typedef struct
+{
+	pid_t		pid;
+	int64		count;
+	TimeOffset	elapsed_time;
+} test_result;
+
+/* Parameters for a test run, passed to workers. */
+typedef struct
+{
+	int			loops;
+	int			num_allocs;
+	int			min_alloc;
+	int			max_alloc;
+	test_mode	mode;
+	test_result results[FLEXIBLE_ARRAY_MEMBER];
+} test_parameters;
+
+/* The startup message given to each worker. */
+typedef struct
+{
+	/* How to connect to the shmem area. */
+	dsa_handle	area_handle;
+	/* Where to find the parameters. */
+	dsa_pointer parameters;
+	/* What index this worker should write results to. */
+	size_t		output_index;
+} test_hello;
+
+static test_mode
+parse_test_mode(text *mode)
+{
+	test_mode	result = MODE_RANDOM;
+	char	   *cstr = text_to_cstring(mode);
+
+	if (strcmp(cstr, "random") == 0)
+		result = MODE_RANDOM;
+	else if (strcmp(cstr, "forwards") == 0)
+		result = MODE_FORWARDS;
+	else if (strcmp(cstr, "backwards") == 0)
+		result = MODE_BACKWARDS;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("unknown mode")));
+	return result;
+}
+
+static void
+check_parameters(const test_parameters *parameters)
+{
+	if (parameters->min_alloc < 1 || parameters->min_alloc > parameters->max_alloc)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("min_alloc must be >= 1, and min_alloc must be <= max_alloc")));
+}
+
+static int
+my_tranche_id(void)
+{
+	static int	tranche_id = 0;
+
+	if (tranche_id == 0)
+		tranche_id = LWLockNewTrancheId();
+
+	return tranche_id;
+}
+
+static void
+do_random_test(dsa_area *area, size_t output_index, test_parameters *parameters)
+{
+	dsa_pointer *objects;
+	int			min_alloc;
+	int			extra_alloc;
+	int32		i;
+	int32		loop;
+	TimestampTz start_time = GetCurrentTimestamp();
+	int64		total_allocations = 0;
+
+	/*
+	 * Make tests reproducible (on the same computer at least) by using the
+	 * same random sequence every time.
+	 */
+	srand(42);
+
+	min_alloc = parameters->min_alloc;
+	extra_alloc = parameters->max_alloc - parameters->min_alloc;
+
+	objects = palloc(sizeof(dsa_pointer) * parameters->num_allocs);
+	Assert(objects != NULL);
+	for (loop = 0; loop < parameters->loops; ++loop)
+	{
+		int			num_actually_allocated = 0;
+
+		for (i = 0; i < parameters->num_allocs; ++i)
+		{
+			size_t		size;
+			void	   *memory;
+
+			/* Adjust size randomly if needed. */
+			size = min_alloc;
+			if (extra_alloc > 0)
+				size += rand() % extra_alloc;
+
+			/* Allocate! */
+			objects[i] = dsa_allocate_extended(area, size, DSA_ALLOC_NO_OOM);
+			if (!DsaPointerIsValid(objects[i]))
+			{
+				elog(LOG, "dsa: loop %d: out of memory after allocating %d objects", loop, i + 1);
+				break;
+			}
+			++num_actually_allocated;
+			/* Pay the cost of accessing that memory */
+			memory = dsa_get_address(area, objects[i]);
+			memset(memory, 42, size);
+		}
+		if (parameters->mode == MODE_RANDOM)
+		{
+			for (i = 0; i < num_actually_allocated; ++i)
+			{
+				size_t		x = rand() % num_actually_allocated;
+				size_t		y = rand() % num_actually_allocated;
+				dsa_pointer temp = objects[x];
+
+				objects[x] = objects[y];
+				objects[y] = temp;
+			}
+		}
+		if (parameters->mode == MODE_BACKWARDS)
+		{
+			for (i = num_actually_allocated - 1; i >= 0; --i)
+				dsa_free(area, objects[i]);
+		}
+		else
+		{
+			for (i = 0; i < num_actually_allocated; ++i)
+				dsa_free(area, objects[i]);
+		}
+		total_allocations += num_actually_allocated;
+	}
+	pfree(objects);
+
+	parameters->results[output_index].elapsed_time = GetCurrentTimestamp() - start_time;
+	parameters->results[output_index].pid = MyProcPid;
+	parameters->results[output_index].count = total_allocations;
+}
+
+/* Non-parallel version: just do it. */
+Datum
+test_dsa_random(PG_FUNCTION_ARGS)
+{
+	test_parameters *parameters;
+	dsa_area   *area;
+
+	parameters =
+		palloc(offsetof(test_parameters, results) + sizeof(test_result));
+	parameters->loops = PG_GETARG_INT32(0);
+	parameters->num_allocs = PG_GETARG_INT32(1);
+	parameters->min_alloc = PG_GETARG_INT32(2);
+	parameters->max_alloc = PG_GETARG_INT32(3);
+	parameters->mode = parse_test_mode(PG_GETARG_TEXT_PP(4));
+	check_parameters(parameters);
+
+	area = dsa_create(my_tranche_id());
+	do_random_test(area, 0, parameters);
+	dsa_dump(area);
+	dsa_detach(area);
+
+	pfree(parameters);
+
+	PG_RETURN_NULL();
+}
+
+void
+test_dsa_random_worker_main(Datum arg)
+{
+	test_hello	hello;
+	dsa_area   *area;
+	test_parameters *parameters;
+
+	CurrentResourceOwner = ResourceOwnerCreate(NULL, "test_dsa toplevel");
+
+	/* Receive hello message and attach to shmem area. */
+	memcpy(&hello, MyBgworkerEntry->bgw_extra, sizeof(hello));
+	area = dsa_attach(hello.area_handle);
+	Assert(area != NULL);
+	parameters = dsa_get_address(area, hello.parameters);
+	Assert(parameters != NULL);
+
+	do_random_test(area, hello.output_index, parameters);
+
+	dsa_detach(area);
+}
+
+/* Parallel version: fork a bunch of background workers to do it. */
+Datum
+test_dsa_random_parallel(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	test_hello	hello;
+	test_parameters *parameters;
+	dsa_area   *area;
+	int			workers;
+	int			i;
+	BackgroundWorkerHandle **handles;
+
+	/* tuplestore boilerplate stuff... */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not " \
+						"allowed in this context")));
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Prepare to work! */
+	workers = PG_GETARG_INT32(5);
+	handles = palloc(sizeof(BackgroundWorkerHandle *) * workers);
+
+	/* Set up the shared memory area. */
+	area = dsa_create(my_tranche_id());
+
+	/* The workers then will attach to it. */
+	hello.area_handle = dsa_get_handle(area);
+
+	/* Allocate space for the parameters object. */
+	hello.parameters = dsa_allocate(area,
+									offsetof(test_parameters, results) +
+									sizeof(test_result) * workers);
+	Assert(DsaPointerIsValid(hello.parameters));
+
+	/* Set up the parameters object. */
+	parameters = dsa_get_address(area, hello.parameters);
+	parameters->loops = PG_GETARG_INT32(0);
+	parameters->num_allocs = PG_GETARG_INT32(1);
+	parameters->min_alloc = PG_GETARG_INT32(2);
+	parameters->max_alloc = PG_GETARG_INT32(3);
+	parameters->mode = parse_test_mode(PG_GETARG_TEXT_PP(4));
+	check_parameters(parameters);
+
+	/* Start the workers. */
+	for (i = 0; i < workers; ++i)
+	{
+		BackgroundWorker bgw;
+
+		memset(&bgw, 0, sizeof(bgw));
+		snprintf(bgw.bgw_name, sizeof(bgw.bgw_name), "worker%d", i);
+		bgw.bgw_flags = BGWORKER_SHMEM_ACCESS;
+		bgw.bgw_start_time = BgWorkerStart_PostmasterStart;
+		bgw.bgw_restart_time = BGW_NEVER_RESTART;
+		snprintf(bgw.bgw_library_name, sizeof(bgw.bgw_library_name),
+				 "test_dsa");
+		snprintf(bgw.bgw_function_name, sizeof(bgw.bgw_function_name),
+				 "test_dsa_random_worker_main");
+		Assert(sizeof(parameters) <= BGW_EXTRALEN);
+		/* Each worker will write its output to a different slot. */
+		hello.output_index = i;
+		memcpy(bgw.bgw_extra, &hello, sizeof(hello));
+		bgw.bgw_notify_pid = MyProcPid;
+
+		if (!RegisterDynamicBackgroundWorker(&bgw, &handles[i]))
+			elog(ERROR, "can't start worker");
+	}
+
+	/* Wait for the workers to complete. */
+	for (i = 0; i < workers; ++i)
+	{
+		BgwHandleStatus status;
+
+		status = WaitForBackgroundWorkerShutdown(handles[i]);
+		if (status == BGWH_POSTMASTER_DIED)
+			proc_exit(1);
+		Assert(status == BGWH_STOPPED);
+	}
+
+	/* Generate result tuples. */
+	for (i = 0; i < workers; ++i)
+	{
+		Datum		values[3];
+		bool		nulls[] = {false, false, false};
+		Interval   *interval = palloc(sizeof(Interval));
+
+		interval->month = 0;
+		interval->day = 0;
+		interval->time = parameters->results[i].elapsed_time;
+
+		values[0] = Int32GetDatum(parameters->results[i].pid);
+		values[1] = Int64GetDatum(parameters->results[i].count);
+		values[2] = PointerGetDatum(interval);
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+	tuplestore_donestoring(tupstore);
+
+	pfree(handles);
+	dsa_detach(area);
+
+	return (Datum) 0;
+}
+
+/* Allocate memory until OOM, than free and try allocate again. */
+Datum
+test_dsa_oom(PG_FUNCTION_ARGS)
+{
+	test_parameters *parameters;
+	dsa_area   *area;
+	int64		cnt1,
+				cnt2;
+
+	parameters =
+		palloc(offsetof(test_parameters, results) + sizeof(test_result));
+
+	parameters->loops = 1;
+	parameters->min_alloc = PG_GETARG_INT32(0);
+	parameters->max_alloc = parameters->min_alloc;
+	check_parameters(parameters);
+
+	parameters->num_allocs = 1024 * 1024 / parameters->min_alloc;
+	parameters->mode = MODE_RANDOM;
+
+	/* Cap available memory at 1MB. */
+	area = dsa_create(my_tranche_id());
+	dsa_set_size_limit(area, 1024 * 1024);
+	dsa_dump(area);
+
+	do_random_test(area, 0, parameters);
+	dsa_dump(area);
+	cnt1 = parameters->results[0].count;
+
+	/* And again... */
+	do_random_test(area, 0, parameters);
+	dsa_dump(area);
+	cnt2 = parameters->results[0].count;
+
+	dsa_detach(area);
+	pfree(parameters);
+
+	/* We should have allocated the same amount both times. */
+	Assert(cnt1 == cnt2);
+
+	PG_RETURN_NULL();
+}
diff --git a/src/test/modules/test_dsa/test_dsa.control b/src/test/modules/test_dsa/test_dsa.control
new file mode 100644
index 0000000000..2655c3fccd
--- /dev/null
+++ b/src/test/modules/test_dsa/test_dsa.control
@@ -0,0 +1,5 @@
+# dsa_test extension
+comment = 'Tests for DSA'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsa'
+relocatable = true
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 260854747b..fe4af1a111 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3769,8 +3769,12 @@ symbol
 tablespaceinfo
 teSection
 temp_tablespaces_extra
+test_hello
+test_mode
+test_parameters
 test_re_flags
 test_regex_ctx
+test_result
 test_shm_mq_header
 test_spec
 test_start_function
-- 
2.39.2

Reply via email to