/* * Another PoC of MemoryContext for DSA based on Thomus Munro's work * * https://www.postgresql.org/message-id/ * CAEepm%3D22H0TDCAHWh%3DYWmSV%2BX%2BbTtcTNg8RpP%3DeaCjWJU_d-9A * %40mail.gmail.com * */ #include "postgres.h" #include "fmgr.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/dsa.h" #include "utils/memutils.h" #define MY_AREA_SIZE (1024 * 1024) #define NUM_CHUNKS 64 /* an arbitrary number */ PG_MODULE_MAGIC; void _PG_init(void); PG_FUNCTION_INFO_V1(hoge); PG_FUNCTION_INFO_V1(hoge_list); PG_FUNCTION_INFO_V1(hoge_list_error); #define isLocalContext(c) (c->alloc_buffer != NULL) #define isBufferFull(buf) (buf->tail == NUM_CHUNKS) /* Support code to make a dsa_area into a MemoryContext. */ typedef struct dsa_temp_buffer dsa_temp_buffer; static void hoge_shmem_startup_hook(void); static MemoryContext make_hoge_memory_context(dsa_area *area, void *base, bool isLocal); static void push_new_buffer(dsa_temp_buffer *buffer); 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 AddToDsaSharedContext(MemoryContext local, MemoryContext shared); MemoryContext CreateDsaLocalContext(MemoryContext shared); Size ShmDsaContextSize(void); 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 = hoge_shmem_startup_hook; } static void hoge_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("hoge", 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("hoge_list", MY_AREA_SIZE, &found); if (!found) *my_list = NIL; /* Create a memory context. */ my_shared_dsa_context = make_hoge_memory_context(my_area, my_raw_memory, false); LWLockRelease(AddinShmemInitLock); MemoryContextSwitchTo(old_context); } Datum hoge(PG_FUNCTION_ARGS) { char *s; MemoryContext old_context; old_context = MemoryContextSwitchTo(TopTransactionContext); my_local_dsa_context = CreateDsaLocalContext(my_shared_dsa_context); MemoryContextSwitchTo(old_context); old_context = MemoryContextSwitchTo(my_local_dsa_context); /* Simple smoke test: allocate and free immediately. */ s = pstrdup("hello world"); pfree(s); AddToDsaSharedContext(my_local_dsa_context, my_shared_dsa_context); MemoryContextSwitchTo(old_context); PG_RETURN_VOID(); } Datum hoge_list(PG_FUNCTION_ARGS) { int i = PG_GETARG_INT32(0); ListCell *lc; MemoryContext old_context; old_context = MemoryContextSwitchTo(TopTransactionContext); my_local_dsa_context = CreateDsaLocalContext(my_shared_dsa_context); MemoryContextSwitchTo(old_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); AddToDsaSharedContext(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(); } /* * Error test to check chunk is dsa_freed * Currently after this function is called by user * process goes down because it tries to access dangling pointer */ Datum hoge_list_error(PG_FUNCTION_ARGS) { int i = PG_GETARG_INT32(0); ListCell *lc; MemoryContext old_context; old_context = MemoryContextSwitchTo(TopTransactionContext); my_local_dsa_context = CreateDsaLocalContext(my_shared_dsa_context); MemoryContextSwitchTo(old_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); elog(ERROR, "error test"); LWLockRelease(AddinShmemInitLock); AddToDsaSharedContext(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(); } /* this buffer is used to free all "local" dsa chunks */ struct dsa_temp_buffer { dsa_temp_buffer *next; /* single linked list */ int tail; /* index of array to be allocated */ dsa_pointer chunks[NUM_CHUNKS]; /* relative address of chunks */ }; struct hoge_memory_context { struct MemoryContextData memory_context; void *base; dsa_area *area; /* array of pointer to dsa_allocated chunks to be freed if error occurs */ dsa_temp_buffer *alloc_buffer; }; static void * hoge_alloc(MemoryContext context, Size size) { struct hoge_memory_context *c = (struct hoge_memory_context *) context; char *chunk_backp; dsa_temp_buffer *buf; /* we only allow palloc in temporary DSA-MemoryContext */ if (!isLocalContext(c)) { elog(ERROR, "hoge_alloc should be run in " "local DSA MemoryContext"); return NULL; } /* if buffer is full, allocate a new buffer */ if (isBufferFull(c->alloc_buffer)) push_new_buffer(c->alloc_buffer); /* Add space for the secret context pointer. */ buf = c->alloc_buffer; buf->chunks[buf->tail] = dsa_allocate(c->area, sizeof(void *) + size); chunk_backp = (char *)c->base + buf->chunks[buf->tail]; buf->tail++; *(void **) chunk_backp = context; return chunk_backp + sizeof(void *); } static void hoge_free(MemoryContext context, void *pointer) { struct hoge_memory_context *c = (struct hoge_memory_context *) 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 = (dsa_pointer) ((char *)chunk_backp - (char *)c->base); dsa_free(c->area, dp); /* early return if shared context */ if (!isLocalContext(c)) return; /* To avoid free twice at MemoryContextDelete, remove its reference */ for (buf = c->alloc_buffer; buf != NULL; buf = buf->next) { for (idx = 0; idx < buf->tail; idx++) { if (buf->chunks[idx] == dp) { buf->chunks[idx] = 0; break; } } } } static void * hoge_realloc(MemoryContext context, void *pointer, Size size) { elog(ERROR, "hoge_realloc not implemented"); return NULL; } static void hoge_reset(MemoryContext context) { elog(ERROR, "hoge_reset not implemented"); } static void hoge_delete(MemoryContext context) { struct hoge_memory_context *c = (struct hoge_memory_context *) context; dsa_temp_buffer *buf = c->alloc_buffer; int idx; /* We don't support MemoryContextDelete if Context is permanent */ if (!isLocalContext(c)) elog(ERROR, "hoge_delete is not supported at permanent DSA MemoryContext"); /* free all chunks and buffers */ while (buf && buf->chunks) { dsa_temp_buffer *next_buf = buf->next; for (idx = 0; idx < buf->tail; idx++) { /* Rewind to the secret start of the chunk */ if (buf->chunks[idx] != 0) { dsa_free(c->area, buf->chunks[idx]); elog(NOTICE, "chunk is dsa_freed"); } } pfree(buf); buf = next_buf; } /* Finally, free the context header */ pfree(c); } static Size hoge_get_chunk_space(MemoryContext context, void *pointer) { elog(ERROR, "hoge_get_chunk_space not implemented"); return 0; } static bool hoge_is_empty(MemoryContext context) { elog(ERROR, "hoge_is_empty not implemented"); return false; } static void hoge_set_state(MemoryContext context, MemoryStatsPrintFunc printfunc, void *passthru, MemoryContextCounters *totals) { elog(ERROR, "hoge_set_state not implemented"); } static void hoge_set_check(MemoryContext context) { } static void push_new_buffer(dsa_temp_buffer *buffer) { MemoryContext old_context; dsa_temp_buffer *new_buffer; old_context = MemoryContextSwitchTo(TopTransactionContext); /* 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); } MemoryContext make_hoge_memory_context(dsa_area *area, void *base, bool isLocal) { static const MemoryContextMethods hoge_methods = { hoge_alloc, hoge_free, hoge_realloc, hoge_reset, hoge_delete, hoge_get_chunk_space, hoge_is_empty, hoge_set_state, #ifdef MEMORY_CONTEXT_CHECKING hoge_set_check #endif }; struct hoge_memory_context *result; bool found; if (isLocal) result = palloc0(sizeof(struct hoge_memory_context)); else { result = (struct hoge_memory_context *) ShmemInitStruct("ShmDsaContext", ShmDsaContextSize(), &found); if (found) return &result->memory_context; } MemoryContextCreate(&result->memory_context, T_SlabContext, /* TODO: this is a lie */ &hoge_methods, NULL, "hoge"); result->base = base; result->area = area; /* If context is shared one, chunks become permanent so temp_buffer is NULL */ if (isLocal) result->alloc_buffer = (dsa_temp_buffer *) palloc0(sizeof(dsa_temp_buffer)); else result->alloc_buffer = NULL; return &result->memory_context; } Size ShmDsaContextSize(void) { return sizeof(struct hoge_memory_context); } MemoryContext CreateDsaLocalContext(MemoryContext shared) { MemoryContext local; struct hoge_memory_context *shared_context = (struct hoge_memory_context *) shared; AssertArg(MemoryContextIsValid(shared)); local = make_hoge_memory_context(shared_context->area, shared_context->base, true); MemoryContextSetParent(local, TopTransactionContext); Assert(MemoryContextIsValid(local)); return local; } /* * AddToDsaSharedContext * * 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 local DSA-MemoryContext. When the function using DSA-MemoryContext make * sure that the memory leak does not happen, AddToDsaSharedContext should be called. * * Assuming following case: when catcache is inserted into hash table on * shared memory and transaction is aborted we wouldn't leak memory because * some eviction alogorithm would clean up useless cache eventually. * */ void AddToDsaSharedContext(MemoryContext local, MemoryContext shared) { struct hoge_memory_context *local_dsa_cxt = (struct hoge_memory_context *) local; dsa_temp_buffer *buf = local_dsa_cxt->alloc_buffer; int idx; AssertArg(MemoryContextIsValid(local)); AssertArg(MemoryContextIsValid(shared)); /* change backpointer to shared MemoryContext */ while (buf && buf->chunks) { dsa_temp_buffer *next_buf = buf->next; for (idx = 0; idx < buf->tail; idx++) { /* Rewind to the secret start of the chunk */ if (buf->chunks[idx] != 0) *(void **)(buf->chunks[idx] + (char *)local_dsa_cxt->base) = shared; } /* initialize head of buffer */ buf->tail = 0; buf = next_buf; } }