From 16f9f44acfe81b9d5d14da3dca809b0e2c69eae6 Mon Sep 17 00:00:00 2001 From: "ideriha.takeshi" 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