From 16f9f44acfe81b9d5d14da3dca809b0e2c69eae6 Mon Sep 17 00:00:00 2001
From: "ideriha.takeshi" <ideriha.takeshi@jp.fujitsu.com>
Date: Thu, 13 Jun 2019 14:32:53 +0900
Subject: [PATCH] Shared memory context backed by DSA and its test

---
 src/backend/utils/mmgr/Makefile                    |   3 +-
 src/backend/utils/mmgr/shm_mcxt.c                  | 533 +++++++++++++++++++++
 src/include/nodes/memnodes.h                       |   3 +-
 src/include/nodes/nodes.h                          |   1 +
 src/include/utils/memutils.h                       |  13 +-
 src/test/modules/test_shm_mcxt/.gitignore          |   3 +
 src/test/modules/test_shm_mcxt/Makefile            |  31 ++
 src/test/modules/test_shm_mcxt/README              |  25 +
 .../test_shm_mcxt/expected/concurrent_test.out     |  30 ++
 .../test_shm_mcxt/specs/concurrent_test.spec       |  25 +
 .../modules/test_shm_mcxt/test_shm_mcxt--1.0.sql   |  14 +
 src/test/modules/test_shm_mcxt/test_shm_mcxt.c     | 183 +++++++
 src/test/modules/test_shm_mcxt/test_shm_mcxt.conf  |   1 +
 .../modules/test_shm_mcxt/test_shm_mcxt.control    |   4 +
 14 files changed, 866 insertions(+), 3 deletions(-)
 create mode 100644 src/backend/utils/mmgr/shm_mcxt.c
 create mode 100644 src/test/modules/test_shm_mcxt/.gitignore
 create mode 100644 src/test/modules/test_shm_mcxt/Makefile
 create mode 100644 src/test/modules/test_shm_mcxt/README
 create mode 100644 src/test/modules/test_shm_mcxt/expected/concurrent_test.out
 create mode 100644 src/test/modules/test_shm_mcxt/specs/concurrent_test.spec
 create mode 100644 src/test/modules/test_shm_mcxt/test_shm_mcxt--1.0.sql
 create mode 100644 src/test/modules/test_shm_mcxt/test_shm_mcxt.c
 create mode 100644 src/test/modules/test_shm_mcxt/test_shm_mcxt.conf
 create mode 100644 src/test/modules/test_shm_mcxt/test_shm_mcxt.control

diff --git a/src/backend/utils/mmgr/Makefile b/src/backend/utils/mmgr/Makefile
index f644c40..37134b8 100644
--- a/src/backend/utils/mmgr/Makefile
+++ b/src/backend/utils/mmgr/Makefile
@@ -12,6 +12,7 @@ subdir = src/backend/utils/mmgr
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = aset.o dsa.o freepage.o generation.o mcxt.o memdebug.o portalmem.o slab.o
+OBJS = aset.o dsa.o freepage.o generation.o mcxt.o memdebug.o portalmem.o slab.o \
+	 shm_mcxt.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/mmgr/shm_mcxt.c b/src/backend/utils/mmgr/shm_mcxt.c
new file mode 100644
index 0000000..9311e1a
--- /dev/null
+++ b/src/backend/utils/mmgr/shm_mcxt.c
@@ -0,0 +1,533 @@
+/*-------------------------------------------------------------------------
+ *
+ * shm_mcxt.c
+ *	  ShmContext allocator definitions.
+ *
+ * ShmContext is a MemoryContext implementation designed for cases where you
+ * want to allocate and free objects in the shared memory by MemoryContext 
+ * API (palloc/pfree).
+ *
+ * Portions Copyright (c) 2017-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/mmgr/shm_mcxt.c
+ *
+ *
+ * NOTE:
+ * 
+ * ShmContext allows allocation in the shared memory via palloc().
+ * This is intended to migrate locally allocated objects into shared memory.
+ * These objects could be plancache, syscache or somthing else. They usually 
+ * allocate memory in local heap by palloc().  * To take advantage of exisiting
+ * code, ShmContext uses dsa_allocate()/dsa_free() as palloc()/pfree(). 
+ * However, dsa_allocate() returns dsa_pointer while palloc() returns native
+ * pointer. And also an object may be a graph structure with pointers.
+ * It needs to remember either native pointer or dsa_pointer. 
+ *
+ * So allow the creation of DSA areas inside the traditional fixed memory 
+ * segment (instead of DSM), in a fixed-sized space reserved by the postmaster. 
+ * In this case, dsa_pointer is directly castable to a raw pointer, which is
+ * common to every process. This fits to regular MemoryContext interface. But
+ * note that the total size is fixed at start up time.
+ *
+ * Now we can put objects into shared memory via palloc(). But without
+ * some garbage collection mechanism, memory leaks will be cluster-wide
+ * and cluster-life-time. Leaked object won't go away even if one backend
+ * exits. 
+ *
+ * To address this issue, ShmContext has two types of context: "temporary" and
+ * "permanent" one. "Temporary" context is located in local regular MemoryContext
+ * and has buffer of dsa_pointers to dsa_allocated objects in order to free them 
+ * all at once at transaction rollback. Once developers think memory leak won't 
+ * happen, you can re-parent these temp objects to permanent context. Permanent 
+ * context exists in the shared memory.
+ * 
+ * API to manipulate "temporary" and "permanent" context.
+ * - CreatePermShmContext()
+ * - CreateTempShmContext()
+ * - ChangeToPermShmContext()
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/ilist.h"
+#include "storage/shmem.h"
+#include "utils/dsa.h"
+#include "utils/memdebug.h"
+#include "utils/memutils.h"
+
+
+#define NUM_CHUNKS 128 /* an arbitrary number */
+
+typedef struct dsa_temp_buffer dsa_temp_buffer;
+
+/*
+ * ShmContext is a specialized memory context supporting dsa area created
+ * above Postmaster-initialized shared memory
+ */
+typedef struct ShmContext
+{
+	MemoryContextData header;	/* Standard memory-context fields */
+
+	/* ShmContext parameters */
+	void	 *base;	/* raw address of Postmaster-initialized shared memory */
+	dsa_area   *area; /* dsa area created-in-place  */
+	/* array of pointers to chunks. If ShmContext is permanent, NULL */
+	dsa_temp_buffer *temp_buffer;
+} ShmContext;
+
+/*
+ * dsa_temp_buffer
+ * 		keeping dsa_pointer to chunks to free them at rollback 
+ * 
+ * Temporary ShmContext have this buffer while permanent one does not.
+ * If buffer becomes full, next buffer is pushed to head.
+ */
+struct dsa_temp_buffer
+{
+	dsa_temp_buffer *next; /* single linked list */
+	int idx;			/* index of array to be allocated */
+	dsa_pointer chunks[NUM_CHUNKS]; /* relative address of chunks */
+};
+
+
+#define isTempShmContext(shm_context) (shm_context->temp_buffer != NULL)
+#define isBufferFull(buf) (buf->idx == NUM_CHUNKS)
+#define dsaptr_to_rawptr(dsa_p, base_p)	\
+	((char *)(base_p) + dsa_p)
+#define rawptr_to_dsaptr(raw_p, base_p)	\
+	((dsa_pointer) ((char *)raw_p - (char *)base_p))
+
+ 
+ /* Helper function for ShmContext API */
+static inline MemoryContext
+CreateShmContextInternal(MemoryContext parent,
+						 const char *name,
+						 dsa_area *area, 
+						 void *base,
+						 bool isTemp);
+static Size ShmContextSize(void);
+static void push_temp_buffer(dsa_temp_buffer *buffer);
+
+
+/*
+ * These functions implement the MemoryContext API for ShmContext.
+ */
+static void *ShmContextAlloc(MemoryContext context, Size size);
+static void ShmContextFree(MemoryContext context, void *pointer);
+static void *ShmContextRealloc(MemoryContext context, void *pointer, Size size);
+static void ShmContextReset(MemoryContext context);
+static void ShmContextDelete(MemoryContext context);
+static Size ShmContextGetChunkSpace(MemoryContext context, void *pointer);
+static bool ShmContextIsEmpty(MemoryContext context);
+static void ShmContextStats(MemoryContext context,
+					  MemoryStatsPrintFunc printfunc, void *passthru,
+					  MemoryContextCounters *totals);
+#ifdef MEMORY_CONTEXT_CHECKING
+static void ShmContextCheck(MemoryContext context);
+#endif
+
+/*
+ * This is the virtual function table for ShmContext contexts.
+ */
+static const MemoryContextMethods ShmContextMethods = {
+	ShmContextAlloc,
+	ShmContextFree,
+	ShmContextRealloc,
+	ShmContextReset,
+	ShmContextDelete,
+	ShmContextGetChunkSpace,
+	ShmContextIsEmpty,
+	ShmContextStats
+#ifdef MEMORY_CONTEXT_CHECKING
+	,ShmContextCheck
+#endif
+};
+
+
+/*
+ * CreatePermShmContext
+ *		Create a new permanent ShmContext context.
+ *
+ * parent: parent context, or NULL if top-level context
+ * name: name of context (must be statically allocated)
+ * area: dsa_area created in place of Postmaster-initialized shared memory
+ * base: address of Postmaster-initialized shared memory
+ *
+ * This context itself is allocated on shared memory.
+ */
+MemoryContext
+CreatePermShmContext(MemoryContext parent,
+					 const char *name,
+					 dsa_area *area, 
+					 void *base)
+{
+	return CreateShmContextInternal(parent, name, area, base, false);
+}
+
+/* 
+ * CreateTempShmContext
+ *		Create temporary ShmContext in local heap by ShmContextCreate 
+ * 
+ * parent: parent context, or NULL if top-level context
+ * name: name of context (must be statically allocated)
+ * perm_context: permanent shared MemoryContext 
+ *
+ * Temp context inherits dsa_area and base address of permanent ShmContext.
+ * This context itself is allocated on the parent context, which must not
+ * be permanent ShmContext.
+ * 
+ */
+MemoryContext
+CreateTempShmContext(MemoryContext parent,
+					 const char *name,
+					 MemoryContext perm_context)
+{
+	ShmContext *shmContext;
+
+	AssertArg(MemoryContextIsValid(perm_context));
+
+	shmContext = (ShmContext *) perm_context;
+	
+	/* perm_context should be permanent one */
+	Assert(!isTempShmContext(shmContext));
+
+	return CreateShmContextInternal(parent, name,
+				 shmContext->area, shmContext->base, true);
+}
+
+
+/*
+ * CreateShmContextInternal
+ *		Work-horse for CreatePermShmContext/CreateTempShmContext
+ *
+ * parent: parent context, or NULL if top-level context
+ * name: name of context (must be statically allocated)
+ * area: dsa_area created in place of Postmaster-initialized shared memory
+ * base: address of Postmaster-initialized shared memory
+ * isTemp: context is temporary?
+ *
+ */
+static inline MemoryContext
+CreateShmContextInternal(MemoryContext parent,
+						 const char *name,
+						 dsa_area *area, 
+						 void *base,
+						 bool isTemp)
+{
+	ShmContext *shmContext;
+	bool found;
+
+	/* 
+	 * If context is temp, allocate it and its buffer in parent context.
+	 * If it is permanent, temp_buffer is not used.
+	 */
+	if (isTemp)
+	{
+   		MemoryContext old_context;
+		
+		if (!parent) 
+			elog(ERROR, "Parent context of temporary shared context"
+				 "is not specified");
+
+   		old_context = MemoryContextSwitchTo(parent);
+
+   		shmContext = palloc0(sizeof(ShmContext));
+   		shmContext->temp_buffer = (dsa_temp_buffer *)
+			palloc0(sizeof(dsa_temp_buffer));
+		
+		MemoryContextSwitchTo(old_context);
+	}
+	else
+	{
+		shmContext = (ShmContext *) 
+			ShmemInitStruct(name, ShmContextSize(), &found);
+	   	shmContext->temp_buffer = NULL;
+	
+		if (found)
+			return &shmContext->header;
+	}
+
+	MemoryContextCreate(&shmContext->header,
+						T_ShmContext,
+						&ShmContextMethods,
+						parent,
+						name);
+
+	shmContext->base = base;
+	shmContext->area = area;
+	
+	return &shmContext->header;
+}
+
+/* 
+ * ShmContextSize
+ *		Size of ShmContext
+ */
+static Size
+ShmContextSize(void)
+{
+	return sizeof(ShmContext);
+}
+
+
+/* 
+ * ChangeToPermShmContext
+ *
+ * We don't want to leak memory in shared memory. Unlike local process, 
+ * memory leak still exists even after local process is terminated.
+ * If error occurs in transaction, we free all dsa_allocated chunks linked
+ * from temporary ShmContext. When you make sure that the memory leak does
+ * not happen, ChangeToPermShmContext should be called.
+ *
+ */
+void
+ChangeToPermShmContext(MemoryContext temp_context, MemoryContext perm_context)
+{	
+	ShmContext *temp_shm_context = (ShmContext *) temp_context;
+	dsa_temp_buffer *buf = temp_shm_context->temp_buffer;
+	int idx;
+
+	/* change backpointer to shared MemoryContext */	
+	while (buf && buf->chunks)
+   	{
+		dsa_temp_buffer *next_buf = buf->next;
+
+		for (idx = 0; idx < buf->idx; idx++) 
+		{
+			/* Rewind to the secret start of the chunk */
+			if (buf->chunks[idx] != 0)
+				*(void **)(buf->chunks[idx] + (char *)temp_shm_context->base)
+						   = perm_context;
+		}
+		/* initialize head of buffer */
+		buf->idx = 0;
+		buf = next_buf;
+	}
+}
+
+/* 
+ * push_temp_buffer
+ * 		If temp_buffer becomes full, add new buffer
+ */
+static void
+push_temp_buffer(dsa_temp_buffer *buffer)
+{
+	MemoryContext old_context;
+	dsa_temp_buffer *new_buffer;
+
+	/* choose the same context as current buffer to make lifetime consistent */
+	old_context = MemoryContextSwitchTo(GetMemoryChunkContext(buffer));
+	
+	/* insert a new buffer into head position */
+	new_buffer = (dsa_temp_buffer *) palloc0(sizeof(dsa_temp_buffer));
+	new_buffer->next = buffer;
+	buffer = new_buffer;
+	
+	MemoryContextSwitchTo(old_context);
+}
+
+
+/*
+ * ShmContextReset
+ * 		Free all the memory registered in temp_buffer and buffer
+ *
+ * The chunks registered in temp_buffer are dsa_freed.
+ * This does not affect permanent context. 
+ *
+ */
+static void
+ShmContextReset(MemoryContext context)
+{
+	int			idx;
+ 	dsa_temp_buffer *buf;
+	ShmContext  *shmContext = (ShmContext *) context;
+
+	Assert(shmContext);
+
+   	/* We don't support reset if context is permanent */
+	if (!isTempShmContext(shmContext))
+	   elog(ERROR, 
+			"reset is not supported at permanent DSA MemoryContext");
+
+
+	buf = shmContext->temp_buffer;
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	/* Check for corruption and leaks before freeing */
+	ShmContextCheck(context);
+#endif
+
+
+	/* free all chunks and buffers */
+	while (buf && buf->chunks)
+   	{
+		dsa_temp_buffer *next_buf = buf->next;
+		
+		for (idx = 0; idx < buf->idx; idx++) 
+		{
+			/* chunks may be already freed */
+			if (buf->chunks[idx] != 0)
+				dsa_free(shmContext->area, buf->chunks[idx]);
+		}
+
+		pfree(buf);
+		buf = next_buf;
+	}
+}
+
+/*
+ * ShmContextDelete
+ *  	Free all the memory registered in temp_buffer and context.
+ *		See ShmContextReset.
+ */
+static void
+ShmContextDelete(MemoryContext context)
+{
+	/* Reset to release all the temp_buffers */
+	ShmContextReset(context);
+	/* And free the context header */
+	pfree(context);
+}
+
+/*
+ * ShmContextAlloc
+ *		Returns native pointer to allocated memory
+ */
+static void *
+ShmContextAlloc(MemoryContext context, Size size)
+{
+	ShmContext *shmContext = (ShmContext *) context;
+	
+	char *chunk_backp;	
+	dsa_temp_buffer *buf;
+
+	/* we only allow palloc in temporary ShmContext */
+	if (!isTempShmContext(shmContext))
+	{
+		elog(ERROR, "ShmContextAlloc should be run in "
+			 "temporary ShmContext");
+		return NULL;
+	}
+
+	/* if buffer is full, allocate a new buffer */
+	if (isBufferFull(shmContext->temp_buffer))
+		push_temp_buffer(shmContext->temp_buffer);
+
+	/* Add space for the secret context pointer. */
+	buf = shmContext->temp_buffer;
+	buf->chunks[buf->idx] = dsa_allocate(shmContext->area, sizeof(void *) + size);
+	
+	chunk_backp = dsaptr_to_rawptr(buf->chunks[buf->idx], shmContext->base);
+	buf->idx++;
+	*(void **) chunk_backp = context;
+
+	return chunk_backp + sizeof(void *);
+}
+
+
+/*
+ * ShmContextFree
+ *		Frees allocated memory
+ */
+static void
+ShmContextFree(MemoryContext context, void *pointer)
+{
+	ShmContext *shmContext = (ShmContext *) context;
+	dsa_temp_buffer *buf;
+	char *chunk_backp;
+	dsa_pointer dp;
+	int idx;
+
+	/* Rewind to the secret start of the chunk */
+	chunk_backp = (char *) pointer - sizeof(void *);
+	dp = rawptr_to_dsaptr(chunk_backp, shmContext->base);
+
+	dsa_free(shmContext->area, dp);
+
+	/* if permananet, no need to delete its reference from temp_buffer */
+	if (!isTempShmContext(shmContext))
+		return;
+
+	/* To avoid double free by ShmContextDelete, remove its reference */
+	for (buf = shmContext->temp_buffer; buf != NULL; buf = buf->next)
+	{
+		for (idx = 0; idx < buf->idx; idx++) 
+		{
+			if (buf->chunks[idx] == dp)
+			{
+				buf->chunks[idx] = 0;
+				break;
+			}
+		}
+	}	
+}
+
+/*
+ * ShmContextRealloc
+ *
+ *	realloc() is not supported
+ */
+static void *
+ShmContextRealloc(MemoryContext context, void *pointer, Size size)
+{	
+	elog(ERROR, "ShmContext does not support realloc()");
+	return NULL;				/* keep compiler quiet */
+}
+
+/*
+ * ShmContextGetChunkSpace
+ *		Given a currently-allocated chunk, determine the total space
+ *		it occupies (including all memory-allocation overhead).
+ */
+static Size
+ShmContextGetChunkSpace(MemoryContext context, void *pointer)
+{
+	elog(ERROR, "ShmContext does not support get_chunk_space()");
+	return 0;				/* keep compiler quiet */
+}
+
+/*
+ * ShmContextIsEmpty
+ *		Is an ShmContext empty of any allocated space?
+ */
+static bool
+ShmContextIsEmpty(MemoryContext context)
+{
+	elog(ERROR, "ShmContext does not support is_empty()");
+	return false;				/* keep compiler quiet */
+}
+
+/*
+ * ShmContextStats
+ *		Compute stats about memory consumption of a ShmContext context.
+ *
+ * XXX: can dsa_dump be used?
+ * printfunc: if not NULL, pass a human-readable stats string to this.
+ * passthru: pass this pointer through to printfunc.
+ * totals: if not NULL, add stats about this context into *totals.
+ */
+static void
+ShmContextStats(MemoryContext context,
+		  MemoryStatsPrintFunc printfunc, void *passthru,
+		  MemoryContextCounters *totals)
+{
+	elog(ERROR, "ShmContext does not support stats()");
+}
+
+
+#ifdef MEMORY_CONTEXT_CHECKING
+
+/*
+ * ShmContextCheck
+ *
+ * XXX: for now, do nothing
+ */
+static void
+ShmContextCheck(MemoryContext context)
+{
+
+}
+
+#endif							/* MEMORY_CONTEXT_CHECKING */
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index dbae98d..420601d 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -102,6 +102,7 @@ typedef struct MemoryContextData
 	((context) != NULL && \
 	 (IsA((context), AllocSetContext) || \
 	  IsA((context), SlabContext) || \
-	  IsA((context), GenerationContext)))
+	  IsA((context), GenerationContext) || \
+	  IsA((context), ShmContext)))
 
 #endif							/* MEMNODES_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 4e2fb39..9b1786f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -280,6 +280,7 @@ typedef enum NodeTag
 	T_AllocSetContext,
 	T_SlabContext,
 	T_GenerationContext,
+	T_ShmContext,
 
 	/*
 	 * TAGS FOR VALUE NODES (value.h)
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index ffe6de5..607951f 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -18,7 +18,7 @@
 #define MEMUTILS_H
 
 #include "nodes/memnodes.h"
-
+#include "utils/dsa.h"
 
 /*
  * MaxAllocSize, MaxAllocHugeSize
@@ -181,6 +181,17 @@ extern MemoryContext GenerationContextCreate(MemoryContext parent,
 											 const char *name,
 											 Size blockSize);
 
+/* shm_mcxt.c */
+extern MemoryContext CreatePermShmContext(MemoryContext parent,
+										  const char *name,
+										  dsa_area *area, 
+										  void *base);
+extern MemoryContext CreateTempShmContext(MemoryContext parent,
+										  const char *name,
+										  MemoryContext perm_context);
+extern void ChangeToPermShmContext(MemoryContext temp_context,
+								   MemoryContext perm_context);
+
 /*
  * Recommended default alloc parameters, suitable for "ordinary" contexts
  * that might hold quite a lot of data.
diff --git a/src/test/modules/test_shm_mcxt/.gitignore b/src/test/modules/test_shm_mcxt/.gitignore
new file mode 100644
index 0000000..ba2160b
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/output_iso/
+/tmp_check_iso/
diff --git a/src/test/modules/test_shm_mcxt/Makefile b/src/test/modules/test_shm_mcxt/Makefile
new file mode 100644
index 0000000..5a05491
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/Makefile
@@ -0,0 +1,31 @@
+# src/test/modules/test_shm_mcxt/Makefile
+
+MODULES = test_shm_mcxt
+EXTENSION = test_shm_mcxt
+DATA = test_shm_mcxt--1.0.sql
+PGFILEDESC = "test_shm_mcxt - example use of shared memory context"
+
+ISOLATION = concurrent_test
+
+# enable our module in shared_preload_libraries
+ISOLATION_OPTS = --temp-config $(top_srcdir)/src/test/modules/test_shm_mcxt/test_shm_mcxt.conf
+
+# Disabled because these tests require "shared_preload_libraries=test_shm_mcxt",
+# which typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_shm_mcxt
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+# But it can nonetheless be very helpful to run tests on preexisting
+# installation, allow to do so, but only if requested explicitly.
+installcheck-force:
+	$(pg_isolation_regress_installcheck) $(ISOLATION)
diff --git a/src/test/modules/test_shm_mcxt/README b/src/test/modules/test_shm_mcxt/README
new file mode 100644
index 0000000..867963f
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/README
@@ -0,0 +1,25 @@
+test_shm_mcxt is an example of how to use ShmContext and its facility. It 
+is not intended to do anything useful on its own; rather, it is a
+demonstration of how these facilities can be used, and a unit test of
+those facilities.
+
+This extension allows a backend to put a list of number into the shared
+memory and another backend to get the list. To use this extension, you need
+to add shared_preload_libraries = 'test_shm_mcxt'.
+
+XXX: maybe need more test case
+
+Functions
+=========
+
+set_shared_list(i int)
+  RETURNS void
+
+This function sets positve integer into shared list and if a negative integer
+is specified, delete the corresponding positive integer.
+ 
+
+get_shared_list()
+  RETURNS SETOF integer
+
+This function retunrs intergers registerd to the shared list.
\ No newline at end of file
diff --git a/src/test/modules/test_shm_mcxt/expected/concurrent_test.out b/src/test/modules/test_shm_mcxt/expected/concurrent_test.out
new file mode 100644
index 0000000..a17d880
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/expected/concurrent_test.out
@@ -0,0 +1,30 @@
+Parsed test spec with 2 sessions
+
+starting permutation: add_s1_1 add_s2_2 get_s1 remove_s1_2 remove_s2_1 get_s1
+step add_s1_1: CALL set_shared_list(1);
+step add_s2_2: CALL set_shared_list(2);
+step get_s1: SELECT * from get_shared_list();
+get_shared_list
+
+1              
+2              
+step remove_s1_2: CALL set_shared_list(-2);
+step remove_s2_1: CALL set_shared_list(-1);
+step get_s1: SELECT * from get_shared_list();
+get_shared_list
+
+
+starting permutation: add_s1_1 get_s2 remove_s1_2 get_s1 add_s1_1 remove_s1_1 remove_s2_1
+step add_s1_1: CALL set_shared_list(1);
+step get_s2: SELECT * from get_shared_list();
+get_shared_list
+
+1              
+step remove_s1_2: CALL set_shared_list(-2);
+step get_s1: SELECT * from get_shared_list();
+get_shared_list
+
+1              
+step add_s1_1: CALL set_shared_list(1);
+step remove_s1_1: CALL set_shared_list(-1);
+step remove_s2_1: CALL set_shared_list(-1);
diff --git a/src/test/modules/test_shm_mcxt/specs/concurrent_test.spec b/src/test/modules/test_shm_mcxt/specs/concurrent_test.spec
new file mode 100644
index 0000000..f8d8a0a
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/specs/concurrent_test.spec
@@ -0,0 +1,25 @@
+setup
+{
+    CREATE EXTENSION test_shm_mcxt;
+}
+
+teardown
+{
+    DROP EXTENSION test_shm_mcxt;
+}
+
+session "s1"
+step "add_s1_1" {CALL set_shared_list(1);}
+step "remove_s1_1" {CALL set_shared_list(-1);}
+step "remove_s1_2" {CALL set_shared_list(-2);}
+step "get_s1" {SELECT * from get_shared_list();}
+
+session "s2"
+step "add_s2_2" {CALL set_shared_list(2);}
+step "remove_s2_1" {CALL set_shared_list(-1);}
+step "get_s2" {SELECT * from get_shared_list();}
+
+permutation "add_s1_1" "add_s2_2" "get_s1" "remove_s1_2" "remove_s2_1" "get_s1"
+permutation "add_s1_1" "get_s2" "remove_s1_2" "get_s1"
+
+"add_s1_1" "remove_s1_1" "remove_s2_1"
diff --git a/src/test/modules/test_shm_mcxt/test_shm_mcxt--1.0.sql b/src/test/modules/test_shm_mcxt/test_shm_mcxt--1.0.sql
new file mode 100644
index 0000000..0a219d1
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/test_shm_mcxt--1.0.sql
@@ -0,0 +1,14 @@
+/* src/test/modules/test_shm_mcxt/test_shm_mcxt--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_shm_mcxt" to load this file. \quit
+
+
+CREATE PROCEDURE set_shared_list(i int)
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+CREATE FUNCTION get_shared_list()
+RETURNS SETOF integer
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
diff --git a/src/test/modules/test_shm_mcxt/test_shm_mcxt.c b/src/test/modules/test_shm_mcxt/test_shm_mcxt.c
new file mode 100644
index 0000000..44f26ef
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/test_shm_mcxt.c
@@ -0,0 +1,183 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_shm_mcxt.c
+ *		Code to set up a ShmContext and test it
+ *
+ * Copyright (c) 2013-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_shm_mcxt/test_shm_mcxt.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "funcapi.h"
+#include "lib/ilist.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "nodes/memnodes.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+#define MY_AREA_SIZE (1024 * 1024)
+
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+PG_FUNCTION_INFO_V1(set_shared_list);
+PG_FUNCTION_INFO_V1(get_shared_list);
+
+static void shm_mcxt_shmem_startup_hook(void);
+
+static shmem_startup_hook_type prev_shmem_startup_hook;
+static void *my_raw_memory;
+static dsa_area *my_area;
+static MemoryContext my_shared_dsa_context;
+static MemoryContext my_local_dsa_context;
+
+static List **my_list;
+
+
+void
+_PG_init(void)
+{
+	/* This only works if preloaded by the postmaster. */
+	if (!process_shared_preload_libraries_in_progress)
+		return;
+
+	/* Request a chunk of traditional shared memory. */
+	RequestAddinShmemSpace(MY_AREA_SIZE);
+
+	/* Register our hook for phase II of initialization. */
+	prev_shmem_startup_hook = shmem_startup_hook;
+	shmem_startup_hook = shm_mcxt_shmem_startup_hook;
+}
+
+static void
+shm_mcxt_shmem_startup_hook(void)
+{
+	MemoryContext	old_context;
+	bool		found;
+
+	if (prev_shmem_startup_hook)
+		prev_shmem_startup_hook();
+
+	old_context = MemoryContextSwitchTo(TopMemoryContext);
+
+	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+
+	/* Allocate, or look up, a chunk of raw fixed-address shared memory. */
+	my_raw_memory = ShmemInitStruct("my_area", MY_AREA_SIZE, &found);
+	if (!found)
+	{
+		/*
+		 * Create a new DSA area, and clamp its size so it can't make any
+		 * segments outside the provided space.
+		 */
+		my_area = dsa_create_in_place(my_raw_memory, MY_AREA_SIZE, 0, NULL);
+		dsa_set_size_limit(my_area, MY_AREA_SIZE);
+	}
+	else
+	{
+		/* Attach to an existing area. */
+		my_area = dsa_attach_in_place(my_raw_memory, NULL);
+	}
+
+	/* Also allocate or look up a list header. */
+	my_list = ShmemInitStruct("my_list", MY_AREA_SIZE, &found);
+	if (!found)
+		*my_list = NIL;
+
+	LWLockRelease(AddinShmemInitLock);
+
+	/* Create a memory context. */
+	my_shared_dsa_context = CreatePermShmContext(NULL, "my_shared_context",
+												 my_area, my_raw_memory);
+
+	MemoryContextSwitchTo(old_context);
+}
+
+/* Set the positive number */
+Datum
+set_shared_list(PG_FUNCTION_ARGS)
+{
+	int i = PG_GETARG_INT32(0);
+	ListCell *lc;
+	MemoryContext old_context;
+
+	my_local_dsa_context = CreateTempShmContext(CurrentMemoryContext,
+									  "my_local_context",
+									  my_shared_dsa_context);
+
+	old_context = MemoryContextSwitchTo(my_local_dsa_context);
+
+	/* Manipulate a list in shared memory. */
+	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+	if (i < 0)
+		*my_list = list_delete_int(*my_list, -i);
+	else
+		*my_list = lappend_int(*my_list, i);
+	LWLockRelease(AddinShmemInitLock);
+
+	ChangeToPermShmContext(my_local_dsa_context, my_shared_dsa_context);
+
+	/* Dump list. */
+	elog(NOTICE, "Contents of list:");
+	foreach(lc, *my_list)
+		elog(NOTICE, " %d", lfirst_int(lc));
+
+	MemoryContextSwitchTo(old_context);
+
+	PG_RETURN_VOID();
+}
+
+/* Get the list of intergers registerd to shared list */
+Datum
+get_shared_list(PG_FUNCTION_ARGS)
+{
+   	FuncCallContext    *funcctx;
+	MemoryContext		oldcontext;
+   	int		result;
+	ListCell **lcp;
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		funcctx = SRF_FIRSTCALL_INIT();
+			
+		/*
+		 * Switch to memory context appropriate for multiple function calls
+		 */
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+		
+
+   	   	LWLockAcquire(AddinShmemInitLock, LW_SHARED);
+   		/* allocate memory for user context to hold current cell */
+		lcp = (ListCell **) palloc(sizeof(ListCell *));
+   		*lcp = list_head(*my_list);
+		funcctx->user_fctx = (void *) lcp;
+		*lcp = list_head(*my_list);
+
+		LWLockRelease(AddinShmemInitLock);
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+	
+	/* stuff done on every call of the function */
+	funcctx = SRF_PERCALL_SETUP();
+	lcp = (ListCell **) funcctx->user_fctx;
+
+   	while (*lcp != NULL)
+	{
+		result =  lfirst_int(*lcp);
+		*lcp = lnext(*lcp);
+		SRF_RETURN_NEXT(funcctx, Int32GetDatum(result));
+	}
+
+	SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/test/modules/test_shm_mcxt/test_shm_mcxt.conf b/src/test/modules/test_shm_mcxt/test_shm_mcxt.conf
new file mode 100644
index 0000000..0dbbb48
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/test_shm_mcxt.conf
@@ -0,0 +1 @@
+shared_preload_libraries = 'test_shm_mcxt'
diff --git a/src/test/modules/test_shm_mcxt/test_shm_mcxt.control b/src/test/modules/test_shm_mcxt/test_shm_mcxt.control
new file mode 100644
index 0000000..21aefd2
--- /dev/null
+++ b/src/test/modules/test_shm_mcxt/test_shm_mcxt.control
@@ -0,0 +1,4 @@
+comment = 'Test code for shared memory context'
+default_version = '1.0'
+module_pathname = '$libdir/test_shm_mcxt'
+relocatable = true
\ No newline at end of file
-- 
1.8.3.1

