Safe memory allocation functions

Started by Michael Paquieralmost 11 years ago31 messages
#1Michael Paquier
michael.paquier@gmail.com
1 attachment(s)

Hi all,

For the last couple of weeks it has been mentioned a couple of times
that it would be useful to have a set of palloc APIs able to return
NULL on OOM to allow certain code paths to not ERROR and to take
another route when memory is under pressure. This has been for example
mentioned on the FPW compression thread or here:
/messages/by-id/CAB7nPqRbewhSbJ_tkAogtpcMrxYJsvKKB9p030d0TpijB4t3YA@mail.gmail.com

Attached is a patch adding the following set of functions for frontend
and backends returning NULL instead of reporting ERROR when allocation
fails:
- palloc_safe
- palloc0_safe
- repalloc_safe
This has simply needed some refactoring in aset.c to set up the new
functions by passing an additional control flag, and I didn't think
that adding a new safe version for AllocSetContextCreate was worth it.
Those APIs are not called anywhere yet, but I could for example write
a small extension for that that could be put in src/test/modules or
publish on github in my plugin repo. Also, I am not sure if this is
material for 9.5, even if the patch is not complicated, but let me
know if you are interested in it and I'll add it to the next CF.
Regards,
--
Michael

Attachments:

20150113_palloc_safe.patchtext/x-diff; charset=US-ASCII; name=20150113_palloc_safe.patchDownload
From 008f6bf5a1691fbdf59004157ed521d3b8c41eaf Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 13 Jan 2015 15:40:38 +0900
Subject: [PATCH] Add safe memory allocation APIs able to return NULL on OOM

The following functions are added to the existing set for frontend and
backend:
- palloc_safe
- palloc0_safe
- repalloc_safe
---
 src/backend/utils/mmgr/aset.c    | 529 +++++++++++++++++++++++----------------
 src/backend/utils/mmgr/mcxt.c    | 124 +++++----
 src/common/fe_memutils.c         |  72 ++++--
 src/include/common/fe_memutils.h |   3 +
 src/include/nodes/memnodes.h     |   2 +
 src/include/utils/palloc.h       |   3 +
 6 files changed, 451 insertions(+), 282 deletions(-)

diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 85b3c9a..5911c53 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -243,11 +243,22 @@ typedef struct AllocChunkData
 					((AllocPointer)(((char *)(chk)) + ALLOC_CHUNKHDRSZ))
 
 /*
+ * Wrappers for allocation functions.
+ */
+static void *set_alloc_internal(MemoryContext context,
+								Size size, bool is_safe);
+static void *set_realloc_internal(MemoryContext context, void *pointer,
+								  Size size, bool is_safe);
+
+/*
  * These functions implement the MemoryContext API for AllocSet contexts.
  */
 static void *AllocSetAlloc(MemoryContext context, Size size);
+static void *AllocSetAllocSafe(MemoryContext context, Size size);
 static void AllocSetFree(MemoryContext context, void *pointer);
 static void *AllocSetRealloc(MemoryContext context, void *pointer, Size size);
+static void *AllocSetReallocSafe(MemoryContext context,
+								 void *pointer, Size size);
 static void AllocSetInit(MemoryContext context);
 static void AllocSetReset(MemoryContext context);
 static void AllocSetDelete(MemoryContext context);
@@ -264,8 +275,10 @@ static void AllocSetCheck(MemoryContext context);
  */
 static MemoryContextMethods AllocSetMethods = {
 	AllocSetAlloc,
+	AllocSetAllocSafe,
 	AllocSetFree,
 	AllocSetRealloc,
+	AllocSetReallocSafe,
 	AllocSetInit,
 	AllocSetReset,
 	AllocSetDelete,
@@ -517,140 +530,16 @@ AllocSetContextCreate(MemoryContext parent,
 }
 
 /*
- * AllocSetInit
- *		Context-type-specific initialization routine.
- *
- * This is called by MemoryContextCreate() after setting up the
- * generic MemoryContext fields and before linking the new context
- * into the context tree.  We must do whatever is needed to make the
- * new context minimally valid for deletion.  We must *not* risk
- * failure --- thus, for example, allocating more memory is not cool.
- * (AllocSetContextCreate can allocate memory when it gets control
- * back, however.)
- */
-static void
-AllocSetInit(MemoryContext context)
-{
-	/*
-	 * Since MemoryContextCreate already zeroed the context node, we don't
-	 * have to do anything here: it's already OK.
-	 */
-}
-
-/*
- * AllocSetReset
- *		Frees all memory which is allocated in the given set.
- *
- * Actually, this routine has some discretion about what to do.
- * It should mark all allocated chunks freed, but it need not necessarily
- * give back all the resources the set owns.  Our actual implementation is
- * that we hang onto any "keeper" block specified for the set.  In this way,
- * we don't thrash malloc() when a context is repeatedly reset after small
- * allocations, which is typical behavior for per-tuple contexts.
- */
-static void
-AllocSetReset(MemoryContext context)
-{
-	AllocSet	set = (AllocSet) context;
-	AllocBlock	block;
-
-	AssertArg(AllocSetIsValid(set));
-
-#ifdef MEMORY_CONTEXT_CHECKING
-	/* Check for corruption and leaks before freeing */
-	AllocSetCheck(context);
-#endif
-
-	/* Clear chunk freelists */
-	MemSetAligned(set->freelist, 0, sizeof(set->freelist));
-
-	block = set->blocks;
-
-	/* New blocks list is either empty or just the keeper block */
-	set->blocks = set->keeper;
-
-	while (block != NULL)
-	{
-		AllocBlock	next = block->next;
-
-		if (block == set->keeper)
-		{
-			/* Reset the block, but don't return it to malloc */
-			char	   *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ;
-
-#ifdef CLOBBER_FREED_MEMORY
-			wipe_mem(datastart, block->freeptr - datastart);
-#else
-			/* wipe_mem() would have done this */
-			VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
-#endif
-			block->freeptr = datastart;
-			block->next = NULL;
-		}
-		else
-		{
-			/* Normal case, release the block */
-#ifdef CLOBBER_FREED_MEMORY
-			wipe_mem(block, block->freeptr - ((char *) block));
-#endif
-			free(block);
-		}
-		block = next;
-	}
-
-	/* Reset block size allocation sequence, too */
-	set->nextBlockSize = set->initBlockSize;
-}
-
-/*
- * AllocSetDelete
- *		Frees all memory which is allocated in the given set,
- *		in preparation for deletion of the set.
- *
- * Unlike AllocSetReset, this *must* free all resources of the set.
- * But note we are not responsible for deleting the context node itself.
- */
-static void
-AllocSetDelete(MemoryContext context)
-{
-	AllocSet	set = (AllocSet) context;
-	AllocBlock	block = set->blocks;
-
-	AssertArg(AllocSetIsValid(set));
-
-#ifdef MEMORY_CONTEXT_CHECKING
-	/* Check for corruption and leaks before freeing */
-	AllocSetCheck(context);
-#endif
-
-	/* Make it look empty, just in case... */
-	MemSetAligned(set->freelist, 0, sizeof(set->freelist));
-	set->blocks = NULL;
-	set->keeper = NULL;
-
-	while (block != NULL)
-	{
-		AllocBlock	next = block->next;
-
-#ifdef CLOBBER_FREED_MEMORY
-		wipe_mem(block, block->freeptr - ((char *) block));
-#endif
-		free(block);
-		block = next;
-	}
-}
-
-/*
- * AllocSetAlloc
- *		Returns pointer to allocated memory of given size; memory is added
- *		to the set.
+ * set_alloc_internal
+ *		Wrapper for memory allocation routines.
  *
  * No request may exceed:
  *		MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ
  * All callers use a much-lower limit.
  */
 static void *
-AllocSetAlloc(MemoryContext context, Size size)
+set_alloc_internal(MemoryContext context,
+				   Size size, bool is_safe)
 {
 	AllocSet	set = (AllocSet) context;
 	AllocBlock	block;
@@ -673,10 +562,13 @@ AllocSetAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+			if (is_safe)
+				return NULL;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("out of memory"),
+						 errdetail("Failed on request of size %zu.", size)));
 		}
 		block->aset = set;
 		block->freeptr = block->endptr = ((char *) block) + blksize;
@@ -867,10 +759,13 @@ AllocSetAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+			if (is_safe)
+				return NULL;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("out of memory"),
+						 errdetail("Failed on request of size %zu.", size)));
 		}
 
 		block->aset = set;
@@ -928,83 +823,8 @@ AllocSetAlloc(MemoryContext context, Size size)
 }
 
 /*
- * AllocSetFree
- *		Frees allocated memory; memory is removed from the set.
- */
-static void
-AllocSetFree(MemoryContext context, void *pointer)
-{
-	AllocSet	set = (AllocSet) context;
-	AllocChunk	chunk = AllocPointerGetChunk(pointer);
-
-	AllocFreeInfo(set, chunk);
-
-#ifdef MEMORY_CONTEXT_CHECKING
-	VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
-							  sizeof(chunk->requested_size));
-	/* Test for someone scribbling on unused space in chunk */
-	if (chunk->requested_size < chunk->size)
-		if (!sentinel_ok(pointer, chunk->requested_size))
-			elog(WARNING, "detected write past chunk end in %s %p",
-				 set->header.name, chunk);
-#endif
-
-	if (chunk->size > set->allocChunkLimit)
-	{
-		/*
-		 * Big chunks are certain to have been allocated as single-chunk
-		 * blocks.  Find the containing block and return it to malloc().
-		 */
-		AllocBlock	block = set->blocks;
-		AllocBlock	prevblock = NULL;
-
-		while (block != NULL)
-		{
-			if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ))
-				break;
-			prevblock = block;
-			block = block->next;
-		}
-		if (block == NULL)
-			elog(ERROR, "could not find block containing chunk %p", chunk);
-		/* let's just make sure chunk is the only one in the block */
-		Assert(block->freeptr == ((char *) block) +
-			   (chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ));
-
-		/* OK, remove block from aset's list and free it */
-		if (prevblock == NULL)
-			set->blocks = block->next;
-		else
-			prevblock->next = block->next;
-#ifdef CLOBBER_FREED_MEMORY
-		wipe_mem(block, block->freeptr - ((char *) block));
-#endif
-		free(block);
-	}
-	else
-	{
-		/* Normal case, put the chunk into appropriate freelist */
-		int			fidx = AllocSetFreeIndex(chunk->size);
-
-		chunk->aset = (void *) set->freelist[fidx];
-
-#ifdef CLOBBER_FREED_MEMORY
-		wipe_mem(pointer, chunk->size);
-#endif
-
-#ifdef MEMORY_CONTEXT_CHECKING
-		/* Reset requested_size to 0 in chunks that are on freelist */
-		chunk->requested_size = 0;
-#endif
-		set->freelist[fidx] = chunk;
-	}
-}
-
-/*
- * AllocSetRealloc
- *		Returns new pointer to allocated memory of given size; this memory
- *		is added to the set.  Memory associated with given pointer is copied
- *		into the new memory, and the old memory is freed.
+ * set_realloc_internal
+ *		Wrapper for memory reallocation routines.
  *
  * Without MEMORY_CONTEXT_CHECKING, we don't know the old request size.  This
  * makes our Valgrind client requests less-precise, hazarding false negatives.
@@ -1012,7 +832,8 @@ AllocSetFree(MemoryContext context, void *pointer)
  * request size.)
  */
 static void *
-AllocSetRealloc(MemoryContext context, void *pointer, Size size)
+set_realloc_internal(MemoryContext context, void *pointer,
+					 Size size, bool is_safe)
 {
 	AllocSet	set = (AllocSet) context;
 	AllocChunk	chunk = AllocPointerGetChunk(pointer);
@@ -1029,8 +850,8 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 #endif
 
 	/*
-	 * Chunk sizes are aligned to power of 2 in AllocSetAlloc(). Maybe the
-	 * allocated area already is >= the new size.  (In particular, we always
+	 * Chunk sizes are aligned to power of 2 in set_alloc_internal(). Maybe
+	 * the allocated area already is >= the new size.  (In particular, we always
 	 * fall out here if the requested size is a decrease.)
 	 */
 	if (oldsize >= size)
@@ -1109,10 +930,15 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+
+			/* allocation failed */
+			if (is_safe)
+				return NULL;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("out of memory"),
+						 errdetail("Failed on request of size %zu.", size)));
 		}
 		block->freeptr = block->endptr = ((char *) block) + blksize;
 
@@ -1177,10 +1003,17 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 		AllocPointer newPointer;
 
 		/* allocate new chunk */
-		newPointer = AllocSetAlloc((MemoryContext) set, size);
+		newPointer = set_alloc_internal((MemoryContext) set, size, is_safe);
+
+		/* leave if allocation did not complete properly */
+		if (newPointer == NULL)
+		{
+			Assert(is_safe);
+			return NULL;
+		}
 
 		/*
-		 * AllocSetAlloc() just made the region NOACCESS.  Change it to
+		 * set_alloc_internal() just made the region NOACCESS.  Change it to
 		 * UNDEFINED for the moment; memcpy() will then transfer definedness
 		 * from the old allocation to the new.  If we know the old allocation,
 		 * copy just that much.  Otherwise, make the entire old chunk defined
@@ -1203,6 +1036,258 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 	}
 }
 
+
+/*
+ * AllocSetInit
+ *		Context-type-specific initialization routine.
+ *
+ * This is called by MemoryContextCreate() after setting up the
+ * generic MemoryContext fields and before linking the new context
+ * into the context tree.  We must do whatever is needed to make the
+ * new context minimally valid for deletion.  We must *not* risk
+ * failure --- thus, for example, allocating more memory is not cool.
+ * (AllocSetContextCreate can allocate memory when it gets control
+ * back, however.)
+ */
+static void
+AllocSetInit(MemoryContext context)
+{
+	/*
+	 * Since MemoryContextCreate already zeroed the context node, we don't
+	 * have to do anything here: it's already OK.
+	 */
+}
+
+/*
+ * AllocSetReset
+ *		Frees all memory which is allocated in the given set.
+ *
+ * Actually, this routine has some discretion about what to do.
+ * It should mark all allocated chunks freed, but it need not necessarily
+ * give back all the resources the set owns.  Our actual implementation is
+ * that we hang onto any "keeper" block specified for the set.  In this way,
+ * we don't thrash malloc() when a context is repeatedly reset after small
+ * allocations, which is typical behavior for per-tuple contexts.
+ */
+static void
+AllocSetReset(MemoryContext context)
+{
+	AllocSet	set = (AllocSet) context;
+	AllocBlock	block;
+
+	AssertArg(AllocSetIsValid(set));
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	/* Check for corruption and leaks before freeing */
+	AllocSetCheck(context);
+#endif
+
+	/* Clear chunk freelists */
+	MemSetAligned(set->freelist, 0, sizeof(set->freelist));
+
+	block = set->blocks;
+
+	/* New blocks list is either empty or just the keeper block */
+	set->blocks = set->keeper;
+
+	while (block != NULL)
+	{
+		AllocBlock	next = block->next;
+
+		if (block == set->keeper)
+		{
+			/* Reset the block, but don't return it to malloc */
+			char	   *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ;
+
+#ifdef CLOBBER_FREED_MEMORY
+			wipe_mem(datastart, block->freeptr - datastart);
+#else
+			/* wipe_mem() would have done this */
+			VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
+#endif
+			block->freeptr = datastart;
+			block->next = NULL;
+		}
+		else
+		{
+			/* Normal case, release the block */
+#ifdef CLOBBER_FREED_MEMORY
+			wipe_mem(block, block->freeptr - ((char *) block));
+#endif
+			free(block);
+		}
+		block = next;
+	}
+
+	/* Reset block size allocation sequence, too */
+	set->nextBlockSize = set->initBlockSize;
+}
+
+/*
+ * AllocSetDelete
+ *		Frees all memory which is allocated in the given set,
+ *		in preparation for deletion of the set.
+ *
+ * Unlike AllocSetReset, this *must* free all resources of the set.
+ * But note we are not responsible for deleting the context node itself.
+ */
+static void
+AllocSetDelete(MemoryContext context)
+{
+	AllocSet	set = (AllocSet) context;
+	AllocBlock	block = set->blocks;
+
+	AssertArg(AllocSetIsValid(set));
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	/* Check for corruption and leaks before freeing */
+	AllocSetCheck(context);
+#endif
+
+	/* Make it look empty, just in case... */
+	MemSetAligned(set->freelist, 0, sizeof(set->freelist));
+	set->blocks = NULL;
+	set->keeper = NULL;
+
+	while (block != NULL)
+	{
+		AllocBlock	next = block->next;
+
+#ifdef CLOBBER_FREED_MEMORY
+		wipe_mem(block, block->freeptr - ((char *) block));
+#endif
+		free(block);
+		block = next;
+	}
+}
+
+/*
+ * AllocSetAlloc
+ *		Returns pointer to allocated memory of given size; memory is added
+ *		to the set. This fails with an out-of-memory error if request cannot
+ *		be completed properly.
+ */
+static void *
+AllocSetAlloc(MemoryContext context, Size size)
+{
+	return set_alloc_internal(context, size, false);
+}
+
+/*
+ * AllocSetAllocSafe
+ *		Returns pointer to allocated memory of given size; memory is added
+ *		to the set. This returns NULL if request cannot be completed
+ *		properly.
+ *
+ * No request may exceed:
+ *		MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ
+ * All callers use a much-lower limit.
+ */
+static void *
+AllocSetAllocSafe(MemoryContext context, Size size)
+{
+	return set_alloc_internal(context, size, true);
+}
+
+/*
+ * AllocSetFree
+ *		Frees allocated memory; memory is removed from the set.
+ */
+static void
+AllocSetFree(MemoryContext context, void *pointer)
+{
+	AllocSet	set = (AllocSet) context;
+	AllocChunk	chunk = AllocPointerGetChunk(pointer);
+
+	AllocFreeInfo(set, chunk);
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
+							  sizeof(chunk->requested_size));
+	/* Test for someone scribbling on unused space in chunk */
+	if (chunk->requested_size < chunk->size)
+		if (!sentinel_ok(pointer, chunk->requested_size))
+			elog(WARNING, "detected write past chunk end in %s %p",
+				 set->header.name, chunk);
+#endif
+
+	if (chunk->size > set->allocChunkLimit)
+	{
+		/*
+		 * Big chunks are certain to have been allocated as single-chunk
+		 * blocks.  Find the containing block and return it to malloc().
+		 */
+		AllocBlock	block = set->blocks;
+		AllocBlock	prevblock = NULL;
+
+		while (block != NULL)
+		{
+			if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ))
+				break;
+			prevblock = block;
+			block = block->next;
+		}
+		if (block == NULL)
+			elog(ERROR, "could not find block containing chunk %p", chunk);
+		/* let's just make sure chunk is the only one in the block */
+		Assert(block->freeptr == ((char *) block) +
+			   (chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ));
+
+		/* OK, remove block from aset's list and free it */
+		if (prevblock == NULL)
+			set->blocks = block->next;
+		else
+			prevblock->next = block->next;
+#ifdef CLOBBER_FREED_MEMORY
+		wipe_mem(block, block->freeptr - ((char *) block));
+#endif
+		free(block);
+	}
+	else
+	{
+		/* Normal case, put the chunk into appropriate freelist */
+		int			fidx = AllocSetFreeIndex(chunk->size);
+
+		chunk->aset = (void *) set->freelist[fidx];
+
+#ifdef CLOBBER_FREED_MEMORY
+		wipe_mem(pointer, chunk->size);
+#endif
+
+#ifdef MEMORY_CONTEXT_CHECKING
+		/* Reset requested_size to 0 in chunks that are on freelist */
+		chunk->requested_size = 0;
+#endif
+		set->freelist[fidx] = chunk;
+	}
+}
+
+/*
+ * AllocSetRealloc
+ *		Returns new pointer to allocated memory of given size; this memory
+ *		is added to the set.  Memory associated with given pointer is copied
+ *		into the new memory, and the old memory is freed. If request cannot
+ *		be completed, this fails with an out-of-memory error.
+ */
+static void *
+AllocSetRealloc(MemoryContext context, void *pointer, Size size)
+{
+	return set_realloc_internal(context, pointer, size, false);
+}
+
+/*
+ * AllocSetReallocSafe
+ *		Returns new pointer to allocated memory of given size; this memory
+ *		is added to the set.  Memory associated with given pointer is copied
+ *		into the new memory, and the old memory is freed. If request cannot
+ *		be completed, this returns NULL.
+ */
+static void *
+AllocSetReallocSafe(MemoryContext context, void *pointer, Size size)
+{
+	return set_realloc_internal(context, pointer, size, true);
+}
+
 /*
  * AllocSetGetChunkSpace
  *		Given a currently-allocated chunk, determine the total space
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index aa0d458..7484770 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -56,6 +56,10 @@ MemoryContext PortalContext = NULL;
 
 static void MemoryContextStatsInternal(MemoryContext context, int level);
 
+/* wrapper routines for allocation */
+static void* palloc_internal(Size size, bool is_safe);
+static void* repalloc_internal(void *pointer, Size size, bool is_safe);
+
 /*
  * You should not do memory allocations within a critical section, because
  * an out-of-memory error will be escalated to a PANIC. To enforce that
@@ -684,8 +688,8 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size)
 	return ret;
 }
 
-void *
-palloc(Size size)
+static void*
+palloc_internal(Size size, bool is_safe)
 {
 	/* duplicates MemoryContextAlloc to avoid increased overhead */
 	void	   *ret;
@@ -698,31 +702,85 @@ palloc(Size size)
 
 	CurrentMemoryContext->isReset = false;
 
-	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
+	if (is_safe)
+		ret = (*CurrentMemoryContext->methods->alloc_safe)
+			(CurrentMemoryContext, size);
+	else
+		ret = (*CurrentMemoryContext->methods->alloc)
+			(CurrentMemoryContext, size);
 	VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
 
 	return ret;
 }
 
-void *
-palloc0(Size size)
+static void*
+repalloc_internal(void *pointer, Size size, bool is_safe)
 {
-	/* duplicates MemoryContextAllocZero to avoid increased overhead */
+	MemoryContext context;
 	void	   *ret;
 
-	AssertArg(MemoryContextIsValid(CurrentMemoryContext));
-	AssertNotInCriticalSection(CurrentMemoryContext);
-
 	if (!AllocSizeIsValid(size))
 		elog(ERROR, "invalid memory alloc request size %zu", size);
 
-	CurrentMemoryContext->isReset = false;
+	/*
+	 * Try to detect bogus pointers handed to us, poorly though we can.
+	 * Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
+	 * allocated chunk.
+	 */
+	Assert(pointer != NULL);
+	Assert(pointer == (void *) MAXALIGN(pointer));
 
-	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
-	VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
+	/*
+	 * OK, it's probably safe to look at the chunk header.
+	 */
+	context = ((StandardChunkHeader *)
+			   ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context;
 
+	AssertArg(MemoryContextIsValid(context));
+	AssertNotInCriticalSection(context);
+
+	/* isReset must be false already */
+	Assert(!context->isReset);
+
+	if (is_safe)
+		ret = (*context->methods->realloc_safe) (context, pointer, size);
+	else
+		ret = (*context->methods->realloc) (context, pointer, size);
+
+	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+
+	return ret;
+}
+
+void *
+palloc(Size size)
+{
+	return palloc_internal(size, false);
+}
+
+void *
+palloc_safe(Size size)
+{
+	return palloc_internal(size, true);
+}
+
+void *
+palloc0(Size size)
+{
+	void	   *ret;
+
+	ret = palloc_internal(size, false);
 	MemSetAligned(ret, 0, size);
+	return ret;
+}
 
+void *
+palloc0_safe(Size size)
+{
+	void	   *ret;
+
+	ret = palloc_internal(size, true);
+	MemSetAligned(ret, 0, size);
 	return ret;
 }
 
@@ -757,41 +815,23 @@ pfree(void *pointer)
 
 /*
  * repalloc
- *		Adjust the size of a previously allocated chunk.
+ *		Adjust the size of a previously allocated chunk, failing on OOM.
  */
 void *
 repalloc(void *pointer, Size size)
 {
-	MemoryContext context;
-	void	   *ret;
-
-	if (!AllocSizeIsValid(size))
-		elog(ERROR, "invalid memory alloc request size %zu", size);
-
-	/*
-	 * Try to detect bogus pointers handed to us, poorly though we can.
-	 * Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
-	 * allocated chunk.
-	 */
-	Assert(pointer != NULL);
-	Assert(pointer == (void *) MAXALIGN(pointer));
-
-	/*
-	 * OK, it's probably safe to look at the chunk header.
-	 */
-	context = ((StandardChunkHeader *)
-			   ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context;
-
-	AssertArg(MemoryContextIsValid(context));
-	AssertNotInCriticalSection(context);
-
-	/* isReset must be false already */
-	Assert(!context->isReset);
-
-	ret = (*context->methods->realloc) (context, pointer, size);
-	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+	return repalloc_internal(pointer, size, false);
+}
 
-	return ret;
+/*
+ * repalloc
+ *		Adjust the size of a previously allocated chunk, returning NULL
+ *		on OOM.
+ */
+void *
+repalloc_safe(void *pointer, Size size)
+{
+	return repalloc_internal(pointer, size, true);
 }
 
 /*
diff --git a/src/common/fe_memutils.c b/src/common/fe_memutils.c
index 345221e..3bba799 100644
--- a/src/common/fe_memutils.c
+++ b/src/common/fe_memutils.c
@@ -19,8 +19,8 @@
 
 #include "postgres_fe.h"
 
-void *
-pg_malloc(size_t size)
+static void *
+pg_malloc_internal(size_t size, bool is_safe)
 {
 	void	   *tmp;
 
@@ -28,7 +28,24 @@ pg_malloc(size_t size)
 	if (size == 0)
 		size = 1;
 	tmp = malloc(size);
-	if (!tmp)
+	if (!tmp && !is_safe)
+	{
+		fprintf(stderr, _("out of memory\n"));
+		exit(EXIT_FAILURE);
+	}
+	return tmp;
+}
+
+static void *
+pg_realloc_internal(void *ptr, size_t size, bool is_safe)
+{
+	void	   *tmp;
+
+	/* Avoid unportable behavior of realloc(NULL, 0) */
+	if (ptr == NULL && size == 0)
+		size = 1;
+	tmp = realloc(ptr, size);
+	if (!tmp && !is_safe)
 	{
 		fprintf(stderr, _("out of memory\n"));
 		exit(EXIT_FAILURE);
@@ -37,6 +54,12 @@ pg_malloc(size_t size)
 }
 
 void *
+pg_malloc(size_t size)
+{
+	return pg_malloc_internal(size, false);
+}
+
+void *
 pg_malloc0(size_t size)
 {
 	void	   *tmp;
@@ -49,18 +72,7 @@ pg_malloc0(size_t size)
 void *
 pg_realloc(void *ptr, size_t size)
 {
-	void	   *tmp;
-
-	/* Avoid unportable behavior of realloc(NULL, 0) */
-	if (ptr == NULL && size == 0)
-		size = 1;
-	tmp = realloc(ptr, size);
-	if (!tmp)
-	{
-		fprintf(stderr, _("out of memory\n"));
-		exit(EXIT_FAILURE);
-	}
-	return tmp;
+	return pg_realloc_internal(ptr, size, false);
 }
 
 /*
@@ -100,13 +112,31 @@ pg_free(void *ptr)
 void *
 palloc(Size size)
 {
-	return pg_malloc(size);
+	return pg_malloc_internal(size, false);
+}
+
+void *
+palloc_safe(Size size)
+{
+	return pg_malloc_internal(size, true);
 }
 
 void *
 palloc0(Size size)
 {
-	return pg_malloc0(size);
+	void	   *tmp;
+	tmp = pg_malloc_internal(size, false);
+	MemSet(tmp, 0, size);
+	return tmp;
+}
+
+void *
+palloc0_safe(Size size)
+{
+	void	   *tmp;
+	tmp = pg_malloc_internal(size, true);
+	MemSet(tmp, 0, size);
+	return tmp;
 }
 
 void
@@ -124,5 +154,11 @@ pstrdup(const char *in)
 void *
 repalloc(void *pointer, Size size)
 {
-	return pg_realloc(pointer, size);
+	return pg_realloc_internal(pointer, size, false);
+}
+
+void *
+repalloc_safe(void *pointer, Size size)
+{
+	return pg_realloc_internal(pointer, size, true);
 }
diff --git a/src/include/common/fe_memutils.h b/src/include/common/fe_memutils.h
index f7114c2..977e35d 100644
--- a/src/include/common/fe_memutils.h
+++ b/src/include/common/fe_memutils.h
@@ -19,8 +19,11 @@ extern void pg_free(void *pointer);
 /* Equivalent functions, deliberately named the same as backend functions */
 extern char *pstrdup(const char *in);
 extern void *palloc(Size size);
+extern void *palloc_safe(Size size);
 extern void *palloc0(Size size);
+extern void *palloc0_safe(Size size);
 extern void *repalloc(void *pointer, Size size);
+extern void *repalloc_safe(void *pointer, Size size);
 extern void pfree(void *pointer);
 
 /* sprintf into a palloc'd buffer --- these are in psprintf.c */
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index ca9c3de..df7de1e 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -36,9 +36,11 @@
 typedef struct MemoryContextMethods
 {
 	void	   *(*alloc) (MemoryContext context, Size size);
+	void	   *(*alloc_safe) (MemoryContext context, Size size);
 	/* call this free_p in case someone #define's free() */
 	void		(*free_p) (MemoryContext context, void *pointer);
 	void	   *(*realloc) (MemoryContext context, void *pointer, Size size);
+	void	   *(*realloc_safe) (MemoryContext context, void *pointer, Size size);
 	void		(*init) (MemoryContext context);
 	void		(*reset) (MemoryContext context);
 	void		(*delete_context) (MemoryContext context);
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index ca03f2b..96cfa77 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -50,8 +50,11 @@ extern void *MemoryContextAllocZero(MemoryContext context, Size size);
 extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
 
 extern void *palloc(Size size);
+extern void *palloc_safe(Size size);
 extern void *palloc0(Size size);
+extern void *palloc0_safe(Size size);
 extern void *repalloc(void *pointer, Size size);
+extern void *repalloc_safe(void *pointer, Size size);
 extern void pfree(void *pointer);
 
 /*
-- 
2.2.1

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#1)
Re: Safe memory allocation functions

Michael Paquier <michael.paquier@gmail.com> writes:

Attached is a patch adding the following set of functions for frontend
and backends returning NULL instead of reporting ERROR when allocation
fails:
- palloc_safe
- palloc0_safe
- repalloc_safe

Unimpressed with this naming convention. "_unsafe" would be nearer
the mark ;-)

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3David G Johnston
david.g.johnston@gmail.com
In reply to: Michael Paquier (#1)
Re: Safe memory allocation functions

Michael Paquier wrote

Attached is a patch adding the following set of functions for frontend
and backends returning NULL instead of reporting ERROR when allocation
fails:
- palloc_safe
- palloc0_safe
- repalloc_safe

The only thing I can contribute is paint...I'm not fond of the word "_safe"
and think "_try" would be more informative...in the spirit of try/catch as a
means of error handling/recovery.

David J.

--
View this message in context: http://postgresql.nabble.com/Safe-memory-allocation-functions-tp5833709p5833711.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#2)
Re: Safe memory allocation functions

I wrote:

Michael Paquier <michael.paquier@gmail.com> writes:

Attached is a patch adding the following set of functions for frontend
and backends returning NULL instead of reporting ERROR when allocation
fails:
- palloc_safe
- palloc0_safe
- repalloc_safe

Unimpressed with this naming convention. "_unsafe" would be nearer
the mark ;-)

Less snarkily: "_noerror" would probably fit better with existing
precedents in our code.

However, there is a larger practical problem with this whole concept,
which is that experience should teach us to be very wary of the assumption
that asking for memory the system can't give us will just lead to nice
neat malloc-returns-NULL behavior. Any small perusal of the mailing list
archives will remind you that very often the end result will be SIGSEGV,
OOM kills, unrecoverable trap-on-write when the kernel realizes it can't
honor a copy-on-write promise, yadda yadda. Agreed that it's arguable
that these only occur in misconfigured systems ... but misconfiguration
appears to be the default in a depressingly large fraction of systems.
(This is another reason for "_safe" not being the mot juste :-()

In that light, I'm not really convinced that there's a safe use-case
for a behavior like this. I certainly wouldn't risk asking for a couple
of gigabytes on the theory that I could just ask for less if it fails.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Michael Paquier
michael.paquier@gmail.com
In reply to: Tom Lane (#4)
Re: Safe memory allocation functions

Tom Lane writes:

[blah]
(This is another reason for "_safe" not being the mot juste :-()

My wording was definitely incorrect but I sure you got it: I should
have said "safe on error". noerror or error_safe would are definitely
more correct.

In that light, I'm not really convinced that there's a safe use-case
for a behavior like this. I certainly wouldn't risk asking for a couple
of gigabytes on the theory that I could just ask for less if it fails.

That's as well a matter of documentation. We could add a couple of
lines in for example xfunc.sgml to describe the limitations of such
APIs.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#4)
Re: Safe memory allocation functions

On Tue, Jan 13, 2015 at 10:10 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

However, there is a larger practical problem with this whole concept,
which is that experience should teach us to be very wary of the assumption
that asking for memory the system can't give us will just lead to nice
neat malloc-returns-NULL behavior. Any small perusal of the mailing list
archives will remind you that very often the end result will be SIGSEGV,
OOM kills, unrecoverable trap-on-write when the kernel realizes it can't
honor a copy-on-write promise, yadda yadda. Agreed that it's arguable
that these only occur in misconfigured systems ... but misconfiguration
appears to be the default in a depressingly large fraction of systems.
(This is another reason for "_safe" not being the mot juste :-()

I don't really buy this. It's pretty incredible to think that after a
malloc() failure there is absolutely no hope of carrying on sanely.
If that were true, we wouldn't be able to ereport() out-of-memory
errors at any severity less than FATAL, but of course it doesn't work
that way. Moreover, AllocSetAlloc() contains malloc() and, if that
fails, calls malloc() again with a smaller value, without even
throwing an error.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#6)
Re: Safe memory allocation functions

Robert Haas wrote:

On Tue, Jan 13, 2015 at 10:10 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

However, there is a larger practical problem with this whole concept,
which is that experience should teach us to be very wary of the assumption
that asking for memory the system can't give us will just lead to nice
neat malloc-returns-NULL behavior. Any small perusal of the mailing list
archives will remind you that very often the end result will be SIGSEGV,
OOM kills, unrecoverable trap-on-write when the kernel realizes it can't
honor a copy-on-write promise, yadda yadda. Agreed that it's arguable
that these only occur in misconfigured systems ... but misconfiguration
appears to be the default in a depressingly large fraction of systems.
(This is another reason for "_safe" not being the mot juste :-()

I don't really buy this. It's pretty incredible to think that after a
malloc() failure there is absolutely no hope of carrying on sanely.
If that were true, we wouldn't be able to ereport() out-of-memory
errors at any severity less than FATAL, but of course it doesn't work
that way. Moreover, AllocSetAlloc() contains malloc() and, if that
fails, calls malloc() again with a smaller value, without even
throwing an error.

I understood Tom's point differently: instead of malloc() failing,
malloc() will return a supposedly usable pointer, but later usage of it
will lead to a crash of some sort. We know this does happen in reality,
because people do report it; but we also know how to fix it. And for
systems that have been correctly set up, the new behavior (using some
plan B for when malloc actually fails instead of spuriously succeeding
only to cause a later crash) will be much more convenient.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#7)
Re: Safe memory allocation functions

On Wed, Jan 14, 2015 at 9:42 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Robert Haas wrote:

On Tue, Jan 13, 2015 at 10:10 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

However, there is a larger practical problem with this whole concept,
which is that experience should teach us to be very wary of the assumption
that asking for memory the system can't give us will just lead to nice
neat malloc-returns-NULL behavior. Any small perusal of the mailing list
archives will remind you that very often the end result will be SIGSEGV,
OOM kills, unrecoverable trap-on-write when the kernel realizes it can't
honor a copy-on-write promise, yadda yadda. Agreed that it's arguable
that these only occur in misconfigured systems ... but misconfiguration
appears to be the default in a depressingly large fraction of systems.
(This is another reason for "_safe" not being the mot juste :-()

I don't really buy this. It's pretty incredible to think that after a
malloc() failure there is absolutely no hope of carrying on sanely.
If that were true, we wouldn't be able to ereport() out-of-memory
errors at any severity less than FATAL, but of course it doesn't work
that way. Moreover, AllocSetAlloc() contains malloc() and, if that
fails, calls malloc() again with a smaller value, without even
throwing an error.

I understood Tom's point differently: instead of malloc() failing,
malloc() will return a supposedly usable pointer, but later usage of it
will lead to a crash of some sort. We know this does happen in reality,
because people do report it; but we also know how to fix it. And for
systems that have been correctly set up, the new behavior (using some
plan B for when malloc actually fails instead of spuriously succeeding
only to cause a later crash) will be much more convenient.

Hmm, I understood Tom to be opposing the idea of a palloc variant that
returns NULL on failure, and I understand you to be supporting it.
But maybe I'm confused. Anyway, I support it. I agree that there are
systems (or circumstances?) where malloc is going to succeed and then
the world will blow up later on anyway, but I don't think that means
that an out-of-memory error is the only sensible response to a palloc
failure; returning NULL seems like a sometimes-useful alternative.

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#8)
Re: Safe memory allocation functions

On 2015-01-15 08:40:34 -0500, Robert Haas wrote:

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

palloc_or_null()?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Robert Haas
robertmhaas@gmail.com
In reply to: Andres Freund (#9)
Re: Safe memory allocation functions

On Thu, Jan 15, 2015 at 8:42 AM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2015-01-15 08:40:34 -0500, Robert Haas wrote:

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

palloc_or_null()?

That'd work for me, too.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#8)
Re: Safe memory allocation functions

Robert Haas wrote:

Hmm, I understood Tom to be opposing the idea of a palloc variant that
returns NULL on failure, and I understand you to be supporting it.
But maybe I'm confused.

Your understanding seems correct to me. I was just saying that your
description of Tom's argument to dislike the idea seemed at odds with
what he was actually saying.

Anyway, I support it. I agree that there are
systems (or circumstances?) where malloc is going to succeed and then
the world will blow up later on anyway, but I don't think that means
that an out-of-memory error is the only sensible response to a palloc
failure; returning NULL seems like a sometimes-useful alternative.

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

I liked palloc_noerror() better myself FWIW.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#11)
Re: Safe memory allocation functions

On Fri, Jan 16, 2015 at 12:57 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

I liked palloc_noerror() better myself FWIW.

Voting for palloc_noerror() as well.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#12)
1 attachment(s)
Re: Safe memory allocation functions

On Fri, Jan 16, 2015 at 8:47 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Voting for palloc_noerror() as well.

And here is an updated patch using this naming, added to the next CF as well.
--
Michael

Attachments:

20150116_palloc_noerror.patchtext/x-diff; charset=US-ASCII; name=20150116_palloc_noerror.patchDownload
From b636c809c2f2cb4177bedc2e5a4883a79b61fbc6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 13 Jan 2015 15:40:38 +0900
Subject: [PATCH] Add memory allocation APIs able to return NULL instead of
 ERROR

The following functions are added to the existing set for frontend and
backend:
- palloc_noerror
- palloc0_noerror
- repalloc_noerror
---
 src/backend/utils/mmgr/aset.c    | 529 +++++++++++++++++++++++----------------
 src/backend/utils/mmgr/mcxt.c    | 124 +++++----
 src/common/fe_memutils.c         |  72 ++++--
 src/include/common/fe_memutils.h |   3 +
 src/include/nodes/memnodes.h     |   2 +
 src/include/utils/palloc.h       |   3 +
 6 files changed, 451 insertions(+), 282 deletions(-)

diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 85b3c9a..974e018 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -243,11 +243,22 @@ typedef struct AllocChunkData
 					((AllocPointer)(((char *)(chk)) + ALLOC_CHUNKHDRSZ))
 
 /*
+ * Wrappers for allocation functions.
+ */
+static void *set_alloc_internal(MemoryContext context,
+								Size size, bool noerror);
+static void *set_realloc_internal(MemoryContext context, void *pointer,
+								  Size size, bool noerror);
+
+/*
  * These functions implement the MemoryContext API for AllocSet contexts.
  */
 static void *AllocSetAlloc(MemoryContext context, Size size);
+static void *AllocSetAllocNoError(MemoryContext context, Size size);
 static void AllocSetFree(MemoryContext context, void *pointer);
 static void *AllocSetRealloc(MemoryContext context, void *pointer, Size size);
+static void *AllocSetReallocNoError(MemoryContext context,
+								 void *pointer, Size size);
 static void AllocSetInit(MemoryContext context);
 static void AllocSetReset(MemoryContext context);
 static void AllocSetDelete(MemoryContext context);
@@ -264,8 +275,10 @@ static void AllocSetCheck(MemoryContext context);
  */
 static MemoryContextMethods AllocSetMethods = {
 	AllocSetAlloc,
+	AllocSetAllocNoError,
 	AllocSetFree,
 	AllocSetRealloc,
+	AllocSetReallocNoError,
 	AllocSetInit,
 	AllocSetReset,
 	AllocSetDelete,
@@ -517,140 +530,16 @@ AllocSetContextCreate(MemoryContext parent,
 }
 
 /*
- * AllocSetInit
- *		Context-type-specific initialization routine.
- *
- * This is called by MemoryContextCreate() after setting up the
- * generic MemoryContext fields and before linking the new context
- * into the context tree.  We must do whatever is needed to make the
- * new context minimally valid for deletion.  We must *not* risk
- * failure --- thus, for example, allocating more memory is not cool.
- * (AllocSetContextCreate can allocate memory when it gets control
- * back, however.)
- */
-static void
-AllocSetInit(MemoryContext context)
-{
-	/*
-	 * Since MemoryContextCreate already zeroed the context node, we don't
-	 * have to do anything here: it's already OK.
-	 */
-}
-
-/*
- * AllocSetReset
- *		Frees all memory which is allocated in the given set.
- *
- * Actually, this routine has some discretion about what to do.
- * It should mark all allocated chunks freed, but it need not necessarily
- * give back all the resources the set owns.  Our actual implementation is
- * that we hang onto any "keeper" block specified for the set.  In this way,
- * we don't thrash malloc() when a context is repeatedly reset after small
- * allocations, which is typical behavior for per-tuple contexts.
- */
-static void
-AllocSetReset(MemoryContext context)
-{
-	AllocSet	set = (AllocSet) context;
-	AllocBlock	block;
-
-	AssertArg(AllocSetIsValid(set));
-
-#ifdef MEMORY_CONTEXT_CHECKING
-	/* Check for corruption and leaks before freeing */
-	AllocSetCheck(context);
-#endif
-
-	/* Clear chunk freelists */
-	MemSetAligned(set->freelist, 0, sizeof(set->freelist));
-
-	block = set->blocks;
-
-	/* New blocks list is either empty or just the keeper block */
-	set->blocks = set->keeper;
-
-	while (block != NULL)
-	{
-		AllocBlock	next = block->next;
-
-		if (block == set->keeper)
-		{
-			/* Reset the block, but don't return it to malloc */
-			char	   *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ;
-
-#ifdef CLOBBER_FREED_MEMORY
-			wipe_mem(datastart, block->freeptr - datastart);
-#else
-			/* wipe_mem() would have done this */
-			VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
-#endif
-			block->freeptr = datastart;
-			block->next = NULL;
-		}
-		else
-		{
-			/* Normal case, release the block */
-#ifdef CLOBBER_FREED_MEMORY
-			wipe_mem(block, block->freeptr - ((char *) block));
-#endif
-			free(block);
-		}
-		block = next;
-	}
-
-	/* Reset block size allocation sequence, too */
-	set->nextBlockSize = set->initBlockSize;
-}
-
-/*
- * AllocSetDelete
- *		Frees all memory which is allocated in the given set,
- *		in preparation for deletion of the set.
- *
- * Unlike AllocSetReset, this *must* free all resources of the set.
- * But note we are not responsible for deleting the context node itself.
- */
-static void
-AllocSetDelete(MemoryContext context)
-{
-	AllocSet	set = (AllocSet) context;
-	AllocBlock	block = set->blocks;
-
-	AssertArg(AllocSetIsValid(set));
-
-#ifdef MEMORY_CONTEXT_CHECKING
-	/* Check for corruption and leaks before freeing */
-	AllocSetCheck(context);
-#endif
-
-	/* Make it look empty, just in case... */
-	MemSetAligned(set->freelist, 0, sizeof(set->freelist));
-	set->blocks = NULL;
-	set->keeper = NULL;
-
-	while (block != NULL)
-	{
-		AllocBlock	next = block->next;
-
-#ifdef CLOBBER_FREED_MEMORY
-		wipe_mem(block, block->freeptr - ((char *) block));
-#endif
-		free(block);
-		block = next;
-	}
-}
-
-/*
- * AllocSetAlloc
- *		Returns pointer to allocated memory of given size; memory is added
- *		to the set.
+ * set_alloc_internal
+ *		Wrapper for memory allocation routines.
  *
  * No request may exceed:
  *		MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ
  * All callers use a much-lower limit.
  */
 static void *
-AllocSetAlloc(MemoryContext context, Size size)
+set_alloc_internal(MemoryContext context,
+				   Size size, bool noerror)
 {
 	AllocSet	set = (AllocSet) context;
 	AllocBlock	block;
@@ -673,10 +562,13 @@ AllocSetAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+			if (noerror)
+				return NULL;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("out of memory"),
+						 errdetail("Failed on request of size %zu.", size)));
 		}
 		block->aset = set;
 		block->freeptr = block->endptr = ((char *) block) + blksize;
@@ -867,10 +759,13 @@ AllocSetAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+			if (noerror)
+				return NULL;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("out of memory"),
+						 errdetail("Failed on request of size %zu.", size)));
 		}
 
 		block->aset = set;
@@ -928,83 +823,8 @@ AllocSetAlloc(MemoryContext context, Size size)
 }
 
 /*
- * AllocSetFree
- *		Frees allocated memory; memory is removed from the set.
- */
-static void
-AllocSetFree(MemoryContext context, void *pointer)
-{
-	AllocSet	set = (AllocSet) context;
-	AllocChunk	chunk = AllocPointerGetChunk(pointer);
-
-	AllocFreeInfo(set, chunk);
-
-#ifdef MEMORY_CONTEXT_CHECKING
-	VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
-							  sizeof(chunk->requested_size));
-	/* Test for someone scribbling on unused space in chunk */
-	if (chunk->requested_size < chunk->size)
-		if (!sentinel_ok(pointer, chunk->requested_size))
-			elog(WARNING, "detected write past chunk end in %s %p",
-				 set->header.name, chunk);
-#endif
-
-	if (chunk->size > set->allocChunkLimit)
-	{
-		/*
-		 * Big chunks are certain to have been allocated as single-chunk
-		 * blocks.  Find the containing block and return it to malloc().
-		 */
-		AllocBlock	block = set->blocks;
-		AllocBlock	prevblock = NULL;
-
-		while (block != NULL)
-		{
-			if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ))
-				break;
-			prevblock = block;
-			block = block->next;
-		}
-		if (block == NULL)
-			elog(ERROR, "could not find block containing chunk %p", chunk);
-		/* let's just make sure chunk is the only one in the block */
-		Assert(block->freeptr == ((char *) block) +
-			   (chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ));
-
-		/* OK, remove block from aset's list and free it */
-		if (prevblock == NULL)
-			set->blocks = block->next;
-		else
-			prevblock->next = block->next;
-#ifdef CLOBBER_FREED_MEMORY
-		wipe_mem(block, block->freeptr - ((char *) block));
-#endif
-		free(block);
-	}
-	else
-	{
-		/* Normal case, put the chunk into appropriate freelist */
-		int			fidx = AllocSetFreeIndex(chunk->size);
-
-		chunk->aset = (void *) set->freelist[fidx];
-
-#ifdef CLOBBER_FREED_MEMORY
-		wipe_mem(pointer, chunk->size);
-#endif
-
-#ifdef MEMORY_CONTEXT_CHECKING
-		/* Reset requested_size to 0 in chunks that are on freelist */
-		chunk->requested_size = 0;
-#endif
-		set->freelist[fidx] = chunk;
-	}
-}
-
-/*
- * AllocSetRealloc
- *		Returns new pointer to allocated memory of given size; this memory
- *		is added to the set.  Memory associated with given pointer is copied
- *		into the new memory, and the old memory is freed.
+ * set_realloc_internal
+ *		Wrapper for memory reallocation routines.
  *
  * Without MEMORY_CONTEXT_CHECKING, we don't know the old request size.  This
  * makes our Valgrind client requests less-precise, hazarding false negatives.
@@ -1012,7 +832,8 @@ AllocSetFree(MemoryContext context, void *pointer)
  * request size.)
  */
 static void *
-AllocSetRealloc(MemoryContext context, void *pointer, Size size)
+set_realloc_internal(MemoryContext context, void *pointer,
+					 Size size, bool noerror)
 {
 	AllocSet	set = (AllocSet) context;
 	AllocChunk	chunk = AllocPointerGetChunk(pointer);
@@ -1029,8 +850,8 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 #endif
 
 	/*
-	 * Chunk sizes are aligned to power of 2 in AllocSetAlloc(). Maybe the
-	 * allocated area already is >= the new size.  (In particular, we always
+	 * Chunk sizes are aligned to power of 2 in set_alloc_internal(). Maybe
+	 * the allocated area already is >= the new size.  (In particular, we always
 	 * fall out here if the requested size is a decrease.)
 	 */
 	if (oldsize >= size)
@@ -1109,10 +930,15 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+
+			/* allocation failed */
+			if (noerror)
+				return NULL;
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("out of memory"),
+						 errdetail("Failed on request of size %zu.", size)));
 		}
 		block->freeptr = block->endptr = ((char *) block) + blksize;
 
@@ -1177,10 +1003,17 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 		AllocPointer newPointer;
 
 		/* allocate new chunk */
-		newPointer = AllocSetAlloc((MemoryContext) set, size);
+		newPointer = set_alloc_internal((MemoryContext) set, size, noerror);
+
+		/* leave if allocation did not complete properly */
+		if (newPointer == NULL)
+		{
+			Assert(noerror);
+			return NULL;
+		}
 
 		/*
-		 * AllocSetAlloc() just made the region NOACCESS.  Change it to
+		 * set_alloc_internal() just made the region NOACCESS.  Change it to
 		 * UNDEFINED for the moment; memcpy() will then transfer definedness
 		 * from the old allocation to the new.  If we know the old allocation,
 		 * copy just that much.  Otherwise, make the entire old chunk defined
@@ -1203,6 +1036,258 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 	}
 }
 
+
+/*
+ * AllocSetInit
+ *		Context-type-specific initialization routine.
+ *
+ * This is called by MemoryContextCreate() after setting up the
+ * generic MemoryContext fields and before linking the new context
+ * into the context tree.  We must do whatever is needed to make the
+ * new context minimally valid for deletion.  We must *not* risk
+ * failure --- thus, for example, allocating more memory is not cool.
+ * (AllocSetContextCreate can allocate memory when it gets control
+ * back, however.)
+ */
+static void
+AllocSetInit(MemoryContext context)
+{
+	/*
+	 * Since MemoryContextCreate already zeroed the context node, we don't
+	 * have to do anything here: it's already OK.
+	 */
+}
+
+/*
+ * AllocSetReset
+ *		Frees all memory which is allocated in the given set.
+ *
+ * Actually, this routine has some discretion about what to do.
+ * It should mark all allocated chunks freed, but it need not necessarily
+ * give back all the resources the set owns.  Our actual implementation is
+ * that we hang onto any "keeper" block specified for the set.  In this way,
+ * we don't thrash malloc() when a context is repeatedly reset after small
+ * allocations, which is typical behavior for per-tuple contexts.
+ */
+static void
+AllocSetReset(MemoryContext context)
+{
+	AllocSet	set = (AllocSet) context;
+	AllocBlock	block;
+
+	AssertArg(AllocSetIsValid(set));
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	/* Check for corruption and leaks before freeing */
+	AllocSetCheck(context);
+#endif
+
+	/* Clear chunk freelists */
+	MemSetAligned(set->freelist, 0, sizeof(set->freelist));
+
+	block = set->blocks;
+
+	/* New blocks list is either empty or just the keeper block */
+	set->blocks = set->keeper;
+
+	while (block != NULL)
+	{
+		AllocBlock	next = block->next;
+
+		if (block == set->keeper)
+		{
+			/* Reset the block, but don't return it to malloc */
+			char	   *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ;
+
+#ifdef CLOBBER_FREED_MEMORY
+			wipe_mem(datastart, block->freeptr - datastart);
+#else
+			/* wipe_mem() would have done this */
+			VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
+#endif
+			block->freeptr = datastart;
+			block->next = NULL;
+		}
+		else
+		{
+			/* Normal case, release the block */
+#ifdef CLOBBER_FREED_MEMORY
+			wipe_mem(block, block->freeptr - ((char *) block));
+#endif
+			free(block);
+		}
+		block = next;
+	}
+
+	/* Reset block size allocation sequence, too */
+	set->nextBlockSize = set->initBlockSize;
+}
+
+/*
+ * AllocSetDelete
+ *		Frees all memory which is allocated in the given set,
+ *		in preparation for deletion of the set.
+ *
+ * Unlike AllocSetReset, this *must* free all resources of the set.
+ * But note we are not responsible for deleting the context node itself.
+ */
+static void
+AllocSetDelete(MemoryContext context)
+{
+	AllocSet	set = (AllocSet) context;
+	AllocBlock	block = set->blocks;
+
+	AssertArg(AllocSetIsValid(set));
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	/* Check for corruption and leaks before freeing */
+	AllocSetCheck(context);
+#endif
+
+	/* Make it look empty, just in case... */
+	MemSetAligned(set->freelist, 0, sizeof(set->freelist));
+	set->blocks = NULL;
+	set->keeper = NULL;
+
+	while (block != NULL)
+	{
+		AllocBlock	next = block->next;
+
+#ifdef CLOBBER_FREED_MEMORY
+		wipe_mem(block, block->freeptr - ((char *) block));
+#endif
+		free(block);
+		block = next;
+	}
+}
+
+/*
+ * AllocSetAlloc
+ *		Returns pointer to allocated memory of given size; memory is added
+ *		to the set. This fails with an out-of-memory error if request cannot
+ *		be completed properly.
+ */
+static void *
+AllocSetAlloc(MemoryContext context, Size size)
+{
+	return set_alloc_internal(context, size, false);
+}
+
+/*
+ * AllocSetAllocNoError
+ *		Returns pointer to allocated memory of given size; memory is added
+ *		to the set. This returns NULL if request cannot be completed
+ *		properly.
+ *
+ * No request may exceed:
+ *		MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ
+ * All callers use a much-lower limit.
+ */
+static void *
+AllocSetAllocNoError(MemoryContext context, Size size)
+{
+	return set_alloc_internal(context, size, true);
+}
+
+/*
+ * AllocSetFree
+ *		Frees allocated memory; memory is removed from the set.
+ */
+static void
+AllocSetFree(MemoryContext context, void *pointer)
+{
+	AllocSet	set = (AllocSet) context;
+	AllocChunk	chunk = AllocPointerGetChunk(pointer);
+
+	AllocFreeInfo(set, chunk);
+
+#ifdef MEMORY_CONTEXT_CHECKING
+	VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
+							  sizeof(chunk->requested_size));
+	/* Test for someone scribbling on unused space in chunk */
+	if (chunk->requested_size < chunk->size)
+		if (!sentinel_ok(pointer, chunk->requested_size))
+			elog(WARNING, "detected write past chunk end in %s %p",
+				 set->header.name, chunk);
+#endif
+
+	if (chunk->size > set->allocChunkLimit)
+	{
+		/*
+		 * Big chunks are certain to have been allocated as single-chunk
+		 * blocks.  Find the containing block and return it to malloc().
+		 */
+		AllocBlock	block = set->blocks;
+		AllocBlock	prevblock = NULL;
+
+		while (block != NULL)
+		{
+			if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ))
+				break;
+			prevblock = block;
+			block = block->next;
+		}
+		if (block == NULL)
+			elog(ERROR, "could not find block containing chunk %p", chunk);
+		/* let's just make sure chunk is the only one in the block */
+		Assert(block->freeptr == ((char *) block) +
+			   (chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ));
+
+		/* OK, remove block from aset's list and free it */
+		if (prevblock == NULL)
+			set->blocks = block->next;
+		else
+			prevblock->next = block->next;
+#ifdef CLOBBER_FREED_MEMORY
+		wipe_mem(block, block->freeptr - ((char *) block));
+#endif
+		free(block);
+	}
+	else
+	{
+		/* Normal case, put the chunk into appropriate freelist */
+		int			fidx = AllocSetFreeIndex(chunk->size);
+
+		chunk->aset = (void *) set->freelist[fidx];
+
+#ifdef CLOBBER_FREED_MEMORY
+		wipe_mem(pointer, chunk->size);
+#endif
+
+#ifdef MEMORY_CONTEXT_CHECKING
+		/* Reset requested_size to 0 in chunks that are on freelist */
+		chunk->requested_size = 0;
+#endif
+		set->freelist[fidx] = chunk;
+	}
+}
+
+/*
+ * AllocSetRealloc
+ *		Returns new pointer to allocated memory of given size; this memory
+ *		is added to the set.  Memory associated with given pointer is copied
+ *		into the new memory, and the old memory is freed. If request cannot
+ *		be completed, this fails with an out-of-memory error.
+ */
+static void *
+AllocSetRealloc(MemoryContext context, void *pointer, Size size)
+{
+	return set_realloc_internal(context, pointer, size, false);
+}
+
+/*
+ * AllocSetReallocNoError
+ *		Returns new pointer to allocated memory of given size; this memory
+ *		is added to the set.  Memory associated with given pointer is copied
+ *		into the new memory, and the old memory is freed. If request cannot
+ *		be completed, this returns NULL.
+ */
+static void *
+AllocSetReallocNoError(MemoryContext context, void *pointer, Size size)
+{
+	return set_realloc_internal(context, pointer, size, true);
+}
+
 /*
  * AllocSetGetChunkSpace
  *		Given a currently-allocated chunk, determine the total space
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index aa0d458..37c0669 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -56,6 +56,10 @@ MemoryContext PortalContext = NULL;
 
 static void MemoryContextStatsInternal(MemoryContext context, int level);
 
+/* wrapper routines for allocation */
+static void* palloc_internal(Size size, bool noerror);
+static void* repalloc_internal(void *pointer, Size size, bool noerror);
+
 /*
  * You should not do memory allocations within a critical section, because
  * an out-of-memory error will be escalated to a PANIC. To enforce that
@@ -684,8 +688,8 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size)
 	return ret;
 }
 
-void *
-palloc(Size size)
+static void*
+palloc_internal(Size size, bool noerror)
 {
 	/* duplicates MemoryContextAlloc to avoid increased overhead */
 	void	   *ret;
@@ -698,31 +702,85 @@ palloc(Size size)
 
 	CurrentMemoryContext->isReset = false;
 
-	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
+	if (noerror)
+		ret = (*CurrentMemoryContext->methods->alloc_noerror)
+			(CurrentMemoryContext, size);
+	else
+		ret = (*CurrentMemoryContext->methods->alloc)
+			(CurrentMemoryContext, size);
 	VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
 
 	return ret;
 }
 
-void *
-palloc0(Size size)
+static void*
+repalloc_internal(void *pointer, Size size, bool noerror)
 {
-	/* duplicates MemoryContextAllocZero to avoid increased overhead */
+	MemoryContext context;
 	void	   *ret;
 
-	AssertArg(MemoryContextIsValid(CurrentMemoryContext));
-	AssertNotInCriticalSection(CurrentMemoryContext);
-
 	if (!AllocSizeIsValid(size))
 		elog(ERROR, "invalid memory alloc request size %zu", size);
 
-	CurrentMemoryContext->isReset = false;
+	/*
+	 * Try to detect bogus pointers handed to us, poorly though we can.
+	 * Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
+	 * allocated chunk.
+	 */
+	Assert(pointer != NULL);
+	Assert(pointer == (void *) MAXALIGN(pointer));
 
-	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
-	VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
+	/*
+	 * OK, it's probably safe to look at the chunk header.
+	 */
+	context = ((StandardChunkHeader *)
+			   ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context;
 
+	AssertArg(MemoryContextIsValid(context));
+	AssertNotInCriticalSection(context);
+
+	/* isReset must be false already */
+	Assert(!context->isReset);
+
+	if (noerror)
+		ret = (*context->methods->realloc_noerror) (context, pointer, size);
+	else
+		ret = (*context->methods->realloc) (context, pointer, size);
+
+	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+
+	return ret;
+}
+
+void *
+palloc(Size size)
+{
+	return palloc_internal(size, false);
+}
+
+void *
+palloc_noerror(Size size)
+{
+	return palloc_internal(size, true);
+}
+
+void *
+palloc0(Size size)
+{
+	void	   *ret;
+
+	ret = palloc_internal(size, false);
 	MemSetAligned(ret, 0, size);
+	return ret;
+}
 
+void *
+palloc0_noerror(Size size)
+{
+	void	   *ret;
+
+	ret = palloc_internal(size, true);
+	MemSetAligned(ret, 0, size);
 	return ret;
 }
 
@@ -757,41 +815,23 @@ pfree(void *pointer)
 
 /*
  * repalloc
- *		Adjust the size of a previously allocated chunk.
+ *		Adjust the size of a previously allocated chunk, failing on OOM.
  */
 void *
 repalloc(void *pointer, Size size)
 {
-	MemoryContext context;
-	void	   *ret;
-
-	if (!AllocSizeIsValid(size))
-		elog(ERROR, "invalid memory alloc request size %zu", size);
-
-	/*
-	 * Try to detect bogus pointers handed to us, poorly though we can.
-	 * Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
-	 * allocated chunk.
-	 */
-	Assert(pointer != NULL);
-	Assert(pointer == (void *) MAXALIGN(pointer));
-
-	/*
-	 * OK, it's probably safe to look at the chunk header.
-	 */
-	context = ((StandardChunkHeader *)
-			   ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context;
-
-	AssertArg(MemoryContextIsValid(context));
-	AssertNotInCriticalSection(context);
-
-	/* isReset must be false already */
-	Assert(!context->isReset);
-
-	ret = (*context->methods->realloc) (context, pointer, size);
-	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+	return repalloc_internal(pointer, size, false);
+}
 
-	return ret;
+/*
+ * repalloc
+ *		Adjust the size of a previously allocated chunk, returning NULL
+ *		on OOM.
+ */
+void *
+repalloc_noerror(void *pointer, Size size)
+{
+	return repalloc_internal(pointer, size, true);
 }
 
 /*
diff --git a/src/common/fe_memutils.c b/src/common/fe_memutils.c
index 345221e..fd343ba 100644
--- a/src/common/fe_memutils.c
+++ b/src/common/fe_memutils.c
@@ -19,8 +19,8 @@
 
 #include "postgres_fe.h"
 
-void *
-pg_malloc(size_t size)
+static void *
+pg_malloc_internal(size_t size, bool noerror)
 {
 	void	   *tmp;
 
@@ -28,7 +28,24 @@ pg_malloc(size_t size)
 	if (size == 0)
 		size = 1;
 	tmp = malloc(size);
-	if (!tmp)
+	if (!tmp && !noerror)
+	{
+		fprintf(stderr, _("out of memory\n"));
+		exit(EXIT_FAILURE);
+	}
+	return tmp;
+}
+
+static void *
+pg_realloc_internal(void *ptr, size_t size, bool noerror)
+{
+	void	   *tmp;
+
+	/* Avoid unportable behavior of realloc(NULL, 0) */
+	if (ptr == NULL && size == 0)
+		size = 1;
+	tmp = realloc(ptr, size);
+	if (!tmp && !noerror)
 	{
 		fprintf(stderr, _("out of memory\n"));
 		exit(EXIT_FAILURE);
@@ -37,6 +54,12 @@ pg_malloc(size_t size)
 }
 
 void *
+pg_malloc(size_t size)
+{
+	return pg_malloc_internal(size, false);
+}
+
+void *
 pg_malloc0(size_t size)
 {
 	void	   *tmp;
@@ -49,18 +72,7 @@ pg_malloc0(size_t size)
 void *
 pg_realloc(void *ptr, size_t size)
 {
-	void	   *tmp;
-
-	/* Avoid unportable behavior of realloc(NULL, 0) */
-	if (ptr == NULL && size == 0)
-		size = 1;
-	tmp = realloc(ptr, size);
-	if (!tmp)
-	{
-		fprintf(stderr, _("out of memory\n"));
-		exit(EXIT_FAILURE);
-	}
-	return tmp;
+	return pg_realloc_internal(ptr, size, false);
 }
 
 /*
@@ -100,13 +112,31 @@ pg_free(void *ptr)
 void *
 palloc(Size size)
 {
-	return pg_malloc(size);
+	return pg_malloc_internal(size, false);
+}
+
+void *
+palloc_noerror(Size size)
+{
+	return pg_malloc_internal(size, true);
 }
 
 void *
 palloc0(Size size)
 {
-	return pg_malloc0(size);
+	void	   *tmp;
+	tmp = pg_malloc_internal(size, false);
+	MemSet(tmp, 0, size);
+	return tmp;
+}
+
+void *
+palloc0_noeror(Size size)
+{
+	void	   *tmp;
+	tmp = pg_malloc_internal(size, true);
+	MemSet(tmp, 0, size);
+	return tmp;
 }
 
 void
@@ -124,5 +154,11 @@ pstrdup(const char *in)
 void *
 repalloc(void *pointer, Size size)
 {
-	return pg_realloc(pointer, size);
+	return pg_realloc_internal(pointer, size, false);
+}
+
+void *
+repalloc_noerror(void *pointer, Size size)
+{
+	return pg_realloc_internal(pointer, size, true);
 }
diff --git a/src/include/common/fe_memutils.h b/src/include/common/fe_memutils.h
index f7114c2..0151e6e 100644
--- a/src/include/common/fe_memutils.h
+++ b/src/include/common/fe_memutils.h
@@ -19,8 +19,11 @@ extern void pg_free(void *pointer);
 /* Equivalent functions, deliberately named the same as backend functions */
 extern char *pstrdup(const char *in);
 extern void *palloc(Size size);
+extern void *palloc_noerror(Size size);
 extern void *palloc0(Size size);
+extern void *palloc0_noerror(Size size);
 extern void *repalloc(void *pointer, Size size);
+extern void *repalloc_noerror(void *pointer, Size size);
 extern void pfree(void *pointer);
 
 /* sprintf into a palloc'd buffer --- these are in psprintf.c */
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index ca9c3de..6d61acb 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -36,9 +36,11 @@
 typedef struct MemoryContextMethods
 {
 	void	   *(*alloc) (MemoryContext context, Size size);
+	void	   *(*alloc_noerror) (MemoryContext context, Size size);
 	/* call this free_p in case someone #define's free() */
 	void		(*free_p) (MemoryContext context, void *pointer);
 	void	   *(*realloc) (MemoryContext context, void *pointer, Size size);
+	void	   *(*realloc_noerror) (MemoryContext context, void *pointer, Size size);
 	void		(*init) (MemoryContext context);
 	void		(*reset) (MemoryContext context);
 	void		(*delete_context) (MemoryContext context);
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index ca03f2b..3634a7f 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -50,8 +50,11 @@ extern void *MemoryContextAllocZero(MemoryContext context, Size size);
 extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
 
 extern void *palloc(Size size);
+extern void *palloc_noerror(Size size);
 extern void *palloc0(Size size);
+extern void *palloc0_noerror(Size size);
 extern void *repalloc(void *pointer, Size size);
+extern void *repalloc_noerror(void *pointer, Size size);
 extern void pfree(void *pointer);
 
 /*
-- 
2.2.2

#14Andres Freund
andres@2ndquadrant.com
In reply to: Michael Paquier (#12)
Re: Safe memory allocation functions

On 2015-01-16 08:47:10 +0900, Michael Paquier wrote:

On Fri, Jan 16, 2015 at 12:57 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

I liked palloc_noerror() better myself FWIW.

Voting for palloc_noerror() as well.

I don't like that name. It very well can error out. E.g. because of the
allocation size. And we definitely do not want to ignore that case. How
about palloc_try()?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Andres Freund
andres@2ndquadrant.com
In reply to: Michael Paquier (#13)
Re: Safe memory allocation functions

On 2015-01-16 23:06:12 +0900, Michael Paquier wrote:

/*
+ * Wrappers for allocation functions.
+ */
+static void *set_alloc_internal(MemoryContext context,
+								Size size, bool noerror);
+static void *set_realloc_internal(MemoryContext context, void *pointer,
+								  Size size, bool noerror);
+
+/*
* These functions implement the MemoryContext API for AllocSet contexts.
*/
static void *AllocSetAlloc(MemoryContext context, Size size);
+static void *AllocSetAllocNoError(MemoryContext context, Size size);
static void AllocSetFree(MemoryContext context, void *pointer);
static void *AllocSetRealloc(MemoryContext context, void *pointer, Size size);
+static void *AllocSetReallocNoError(MemoryContext context,
+								 void *pointer, Size size);
static void AllocSetInit(MemoryContext context);
static void AllocSetReset(MemoryContext context);
static void AllocSetDelete(MemoryContext context);
@@ -264,8 +275,10 @@ static void AllocSetCheck(MemoryContext context);
*/
static MemoryContextMethods AllocSetMethods = {
AllocSetAlloc,
+	AllocSetAllocNoError,
AllocSetFree,
AllocSetRealloc,
+	AllocSetReallocNoError,
AllocSetInit,
AllocSetReset,
AllocSetDelete,
@@ -517,140 +530,16 @@ AllocSetContextCreate(MemoryContext parent,
}

Wouldn't it make more sense to change the MemoryContext API to return
NULLs in case of allocation failure and do the error checking in the
mcxt.c callers?

+/* wrapper routines for allocation */
+static void* palloc_internal(Size size, bool noerror);
+static void* repalloc_internal(void *pointer, Size size, bool noerror);
+
/*
* You should not do memory allocations within a critical section, because
* an out-of-memory error will be escalated to a PANIC. To enforce that
@@ -684,8 +688,8 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size)
return ret;
}
-void *
-palloc(Size size)
+static void*
+palloc_internal(Size size, bool noerror)
{
/* duplicates MemoryContextAlloc to avoid increased overhead */
void	   *ret;
@@ -698,31 +702,85 @@ palloc(Size size)

CurrentMemoryContext->isReset = false;

-	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
+	if (noerror)
+		ret = (*CurrentMemoryContext->methods->alloc_noerror)
+			(CurrentMemoryContext, size);
+	else
+		ret = (*CurrentMemoryContext->methods->alloc)
+			(CurrentMemoryContext, size);
VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);

return ret;
}

I'd be rather surprised if these branches won't show up in
profiles. This is really rather hot code. At the very least this helper
function should be inlined. Also, calling the valgrind function on an
allocation failure surely isn't correct.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#11)
Re: Safe memory allocation functions

On Thu, Jan 15, 2015 at 10:57 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Hmm, I understood Tom to be opposing the idea of a palloc variant that
returns NULL on failure, and I understand you to be supporting it.
But maybe I'm confused.

Your understanding seems correct to me. I was just saying that your
description of Tom's argument to dislike the idea seemed at odds with
what he was actually saying.

OK, that may be. I'm not sure.

Anyway, I support it. I agree that there are
systems (or circumstances?) where malloc is going to succeed and then
the world will blow up later on anyway, but I don't think that means
that an out-of-memory error is the only sensible response to a palloc
failure; returning NULL seems like a sometimes-useful alternative.

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

I liked palloc_noerror() better myself FWIW.

I don't care for noerror() because it probably still will error in
some circumstances; just not for OOM.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#16)
Re: Safe memory allocation functions

Robert Haas wrote:

On Thu, Jan 15, 2015 at 10:57 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

I liked palloc_noerror() better myself FWIW.

I don't care for noerror() because it probably still will error in
some circumstances; just not for OOM.

Yes, but that seems fine to me. We have other functions with "noerror"
flags, and they can still fail under some circumstances -- just not if
the error is the most commonly considered scenario in which they fail.
The first example I found is LookupAggNameTypeNames(); there are many
more. I don't think this causes any confusion in practice.

Another precendent we have is something like "missing_ok" as a flag name
in get_object_address() and other places; following that, we could have
this new function as "palloc_oom_ok" or something like that. But it
doesn't seem an improvement to me. (I'm pretty sure we all agree that
this must not be a flag to palloc but rather a new function.)

Of all the ones you proposed above, the one I like the most is
palloc_no_oom, but IMO palloc_noerror is still better.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#17)
Re: Safe memory allocation functions

On 2015-01-16 12:09:25 -0300, Alvaro Herrera wrote:

Robert Haas wrote:

On Thu, Jan 15, 2015 at 10:57 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

I liked palloc_noerror() better myself FWIW.

I don't care for noerror() because it probably still will error in
some circumstances; just not for OOM.

Yes, but that seems fine to me. We have other functions with "noerror"
flags, and they can still fail under some circumstances -- just not if
the error is the most commonly considered scenario in which they fail.

We rely on palloc erroring out on large allocations in a couple places
as a crosscheck. I don't think this argument holds much water.

The first example I found is LookupAggNameTypeNames(); there are many
more. I don't think this causes any confusion in practice.

Another precendent we have is something like "missing_ok" as a flag name
in get_object_address() and other places; following that, we could have
this new function as "palloc_oom_ok" or something like that. But it
doesn't seem an improvement to me. (I'm pretty sure we all agree that
this must not be a flag to palloc but rather a new function.)

Of all the ones you proposed above, the one I like the most is
palloc_no_oom, but IMO palloc_noerror is still better.

Neither seem very accurate. no_oom isn't true because they actually can
cause ooms. _noerror isn't true because they can error out - we
e.g. rely on palloc erroring out when reading toast tuples (to detect
invalid datum lengths) and during parsing of WAL as an additional
defense.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#18)
Re: Safe memory allocation functions

Andres Freund wrote:

On 2015-01-16 12:09:25 -0300, Alvaro Herrera wrote:

Robert Haas wrote:

On Thu, Jan 15, 2015 at 10:57 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I do think that "safe" is the wrong suffix. Maybe palloc_soft_fail()
or palloc_null() or palloc_no_oom() or palloc_unsafe().

I liked palloc_noerror() better myself FWIW.

I don't care for noerror() because it probably still will error in
some circumstances; just not for OOM.

Yes, but that seems fine to me. We have other functions with "noerror"
flags, and they can still fail under some circumstances -- just not if
the error is the most commonly considered scenario in which they fail.

We rely on palloc erroring out on large allocations in a couple places
as a crosscheck. I don't think this argument holds much water.

I don't understand what that has to do with it. Surely we're not going
to have palloc_noerror() not error out when presented with a huge
allocation. My point is just that the "noerror" bit in palloc_noerror()
means that it doesn't fail in OOM, and that there are other causes for
it to error.

One thought I just had is that we also have MemoryContextAllocHuge; are
we going to consider a mixture of both things in the future, i.e. allow
huge allocations to return NULL when OOM? It sounds a bit useless
currently, but it doesn't seem extremely far-fetched that we will need
further flags in the future. (Or, perhaps, we will want to have code
that retries a Huge allocation that returns NULL with a smaller size,
just in case it does work.) Maybe what we need is to turn these things
into flags to a new generic function. Furthermore, I question whether
we really need a "palloc" variant -- I mean, can we live with just the
MemoryContext API instead? As with the "Huge" variant (which does not
have a corresponding palloc equivalent), possible use cases seem very
limited so there's probably not much point in providing a shortcut.

So how about something like

#define ALLOCFLAG_HUGE 0x01
#define ALLOCFLAG_NO_ERROR_ON_OOM 0x02
void *
MemoryContextAllocFlags(MemoryContext context, Size size, int flags);

and perhaps even

#define MemoryContextAllocHuge(cxt, sz) \
MemoryContextAllocFlags(cxt, sz, ALLOCFLAG_HUGE)
for source-level compatibility.

(Now we all agree that palloc() itself is a very hot spot and shouldn't
be touched at all. I don't think these new functions are used as commonly
as that, so the fact that they are slightly slower shouldn't be too
troublesome.)

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#19)
Re: Safe memory allocation functions

On 2015-01-16 12:56:18 -0300, Alvaro Herrera wrote:

Andres Freund wrote:

We rely on palloc erroring out on large allocations in a couple places
as a crosscheck. I don't think this argument holds much water.

I don't understand what that has to do with it. Surely we're not going
to have palloc_noerror() not error out when presented with a huge
allocation.

Precisely. That means it *does* error out in a somewhat expected path.

My point is just that the "noerror" bit in palloc_noerror() means that
it doesn't fail in OOM, and that there are other causes for it to
error.

That description pretty much describes why it's a misnomer, no?

One thought I just had is that we also have MemoryContextAllocHuge; are
we going to consider a mixture of both things in the future, i.e. allow
huge allocations to return NULL when OOM?

I definitely think we should. I'd even say that the usecase is larger
for huge allocations. It'd e.g. be rather nice to first try sorting with
the huge 16GB work mem and then try 8GB/4/1GB if that fails.

It sounds a bit useless
currently, but it doesn't seem extremely far-fetched that we will need
further flags in the future. (Or, perhaps, we will want to have code
that retries a Huge allocation that returns NULL with a smaller size,
just in case it does work.) Maybe what we need is to turn these things
into flags to a new generic function. Furthermore, I question whether
we really need a "palloc" variant -- I mean, can we live with just the
MemoryContext API instead? As with the "Huge" variant (which does not
have a corresponding palloc equivalent), possible use cases seem very
limited so there's probably not much point in providing a shortcut.

I'm fine with not providing a palloc() equivalent, but I also am fine
with having it.

So how about something like

#define ALLOCFLAG_HUGE 0x01
#define ALLOCFLAG_NO_ERROR_ON_OOM 0x02
void *
MemoryContextAllocFlags(MemoryContext context, Size size, int flags);

and perhaps even

#define MemoryContextAllocHuge(cxt, sz) \
MemoryContextAllocFlags(cxt, sz, ALLOCFLAG_HUGE)
for source-level compatibility.

I don't know, this seems a bit awkward to use. Your earlier example with
the *Huge variant that returns a smaller allocation doesn't really
convince me - that'd need a separate API anyway.

I definitely do not want to push the nofail stuff via the
MemoryContextData-> API into aset.c. Imo aset.c should always return
NULL and then mcxt.c should throw the error if in the normal palloc()
function.

(Now we all agree that palloc() itself is a very hot spot and shouldn't
be touched at all. I don't think these new functions are used as commonly
as that, so the fact that they are slightly slower shouldn't be too
troublesome.)

Yea, the speed of the new functions really shouldn't matter.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#20)
Re: Safe memory allocation functions

Andres Freund wrote:

On 2015-01-16 12:56:18 -0300, Alvaro Herrera wrote:

So how about something like

#define ALLOCFLAG_HUGE 0x01
#define ALLOCFLAG_NO_ERROR_ON_OOM 0x02
void *
MemoryContextAllocFlags(MemoryContext context, Size size, int flags);

I don't know, this seems a bit awkward to use. Your earlier example with
the *Huge variant that returns a smaller allocation doesn't really
convince me - that'd need a separate API anyway.

What example was that? My thinking was that the mcxt.c function would
return NULL if the request was not satisfied; only the caller would be
entitled to retry with a smaller size. I was thinking in something like

baseflag = ALLOCFLAG_NO_ERROR_ON_OOM;
reqsz = SomeHugeValue;
while (true)
{
ptr = MemoryContextAllocFlags(cxt, reqsz,
ALLOCFLAG_HUGE | baseflag);
if (ptr != NULL)
break; /* success */

/* too large, retry with a smaller allocation */
reqsz *= 0.75;

/* if under some limit, have it fail next time */
if (reqsz < SomeHugeValue * 0.1)
baseflag = 0;
}
/* by here, you know ptr points to a memory area of size reqsz, which is
between SomeHugeValue * 0.1 and SomeHugeValue. */

Were you thinking of something else?

I definitely do not want to push the nofail stuff via the
MemoryContextData-> API into aset.c. Imo aset.c should always return
NULL and then mcxt.c should throw the error if in the normal palloc()
function.

Sure, that seems reasonable ...

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#19)
Re: Safe memory allocation functions

On Fri, Jan 16, 2015 at 10:56 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

So how about something like

#define ALLOCFLAG_HUGE 0x01
#define ALLOCFLAG_NO_ERROR_ON_OOM 0x02
void *
MemoryContextAllocFlags(MemoryContext context, Size size, int flags);

That sounds good, although personally I'd rather have the name be
something like MemoryContextAllocExtended; we have precedent for using
"Extended" for this sort of thing elsewhere. Also, I'd suggest trying
to keep the flag name short, e.g. ALLOC_HUGE and ALLOC_NO_OOM (or
ALLOC_SOFT_FAIL?).

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#22)
Re: Safe memory allocation functions

On Sat, Jan 17, 2015 at 11:06 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jan 16, 2015 at 10:56 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

So how about something like

#define ALLOCFLAG_HUGE 0x01
#define ALLOCFLAG_NO_ERROR_ON_OOM 0x02
void *
MemoryContextAllocFlags(MemoryContext context, Size size, int flags);

That sounds good, although personally I'd rather have the name be
something like MemoryContextAllocExtended; we have precedent for using
"Extended" for this sort of thing elsewhere. Also, I'd suggest trying
to keep the flag name short, e.g. ALLOC_HUGE and ALLOC_NO_OOM (or
ALLOC_SOFT_FAIL?).

Yes, I think that this name makes more sense (LockAcquire[Extended],
RangeVarGetRelid[Extended]), as well as minimizing shorter name for
the flags.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#21)
Re: Safe memory allocation functions

Alvaro Herrera wrote:

So how about something like

#define ALLOCFLAG_HUGE 0x01
#define ALLOCFLAG_NO_ERROR_ON_OOM 0x02
void *
MemoryContextAllocFlags(MemoryContext context, Size size, int flags);

The flag for huge allocations may be useful, but I don't actually see
much value in the flag ALLOC_NO_OOM if the stuff in aset.c returns
unconditionally NULL in case of an OOM and we let palloc complain
about an OOM when allocation returns NULL. Something I am missing
perhaps?

I definitely do not want to push the nofail stuff via the
MemoryContextData-> API into aset.c. Imo aset.c should always return
NULL and then mcxt.c should throw the error if in the normal palloc()
function.

Sure, that seems reasonable ...

Yes, this would simplify the footprint of this patch to aset.c to a
minimum by changing the ereport to NULL in a couple of places.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Andres Freund
andres@2ndquadrant.com
In reply to: Michael Paquier (#24)
Re: Safe memory allocation functions

On 2015-01-27 17:27:53 +0900, Michael Paquier wrote:

Alvaro Herrera wrote:

So how about something like

#define ALLOCFLAG_HUGE 0x01
#define ALLOCFLAG_NO_ERROR_ON_OOM 0x02
void *
MemoryContextAllocFlags(MemoryContext context, Size size, int flags);

The flag for huge allocations may be useful, but I don't actually see
much value in the flag ALLOC_NO_OOM if the stuff in aset.c returns
unconditionally NULL in case of an OOM and we let palloc complain
about an OOM when allocation returns NULL. Something I am missing
perhaps?

I guess the idea is to have *user facing* MemoryContextAllocExtended()
that can do both huge and no-oom allocations. Otherwise we need palloc
like wrappers for all combinations.
We're certainly not just going to ignore memory allocation failures
generally in in MemoryContextAllocExtended()....

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Michael Paquier
michael.paquier@gmail.com
In reply to: Andres Freund (#25)
3 attachment(s)
Re: Safe memory allocation functions

On Tue, Jan 27, 2015 at 5:34 PM, Andres Freund <andres@2ndquadrant.com> wrote:

On 2015-01-27 17:27:53 +0900, Michael Paquier wrote:

Alvaro Herrera wrote:

So how about something like

#define ALLOCFLAG_HUGE 0x01
#define ALLOCFLAG_NO_ERROR_ON_OOM 0x02
void *
MemoryContextAllocFlags(MemoryContext context, Size size, int flags);

The flag for huge allocations may be useful, but I don't actually see
much value in the flag ALLOC_NO_OOM if the stuff in aset.c returns
unconditionally NULL in case of an OOM and we let palloc complain
about an OOM when allocation returns NULL. Something I am missing
perhaps?

I guess the idea is to have *user facing* MemoryContextAllocExtended()
that can do both huge and no-oom allocations. Otherwise we need palloc
like wrappers for all combinations.
We're certainly not just going to ignore memory allocation failures
generally in in MemoryContextAllocExtended()....

As a result of all the comments on this thread, here are 3 patches
implementing incrementally the different ideas from everybody:
1) 0001 modifies aset.c to return unconditionally NULL in case of an
OOM instead of reporting an error. All the OOM error reports are moved
to mcxt.c (MemoryContextAlloc* and palloc*)
2) 0002 adds the noerror routines for frontend and backend.
3) 0003 adds MemoryContextAllocExtended that can be called with the
following control flags:
#define ALLOC_HUGE 0x01 /* huge allocation */
#define ALLOC_ZERO 0x02 /* clear allocated memory */
#define ALLOC_NO_OOM 0x04 /* no failure if out-of-memory */
#define ALLOC_ALIGNED 0x08 /* request length suitable for MemSetLoop */
This groups MemoryContextAlloc, MemoryContextAllocHuge,
MemoryContextAllocZero and MemoryContextAllocZeroAligned under the
same central routine.
Regards,
--
Michael

Attachments:

0001-Make-allocation-return-functions-return-NULL-on-OOM.patchtext/x-patch; charset=US-ASCII; name=0001-Make-allocation-return-functions-return-NULL-on-OOM.patchDownload
From 337c439554ce66486cf9d29dced6c72d034b8f8d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 28 Jan 2015 22:10:13 +0900
Subject: [PATCH 1/3] Make allocation return functions return NULL on OOM

On counterpart, higher-level APIs in mcxt.c return an explicit error
message when memory requests cannot be completed.
---
 src/backend/utils/mmgr/aset.c | 30 ++++++++++++---------------
 src/backend/utils/mmgr/mcxt.c | 48 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 61 insertions(+), 17 deletions(-)

diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 85b3c9a..bf6f09a 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -642,8 +642,8 @@ AllocSetDelete(MemoryContext context)
 
 /*
  * AllocSetAlloc
- *		Returns pointer to allocated memory of given size; memory is added
- *		to the set.
+ *		Returns pointer to allocated memory of given size or NULL if
+ *		request could not be completed; memory is added to the set.
  *
  * No request may exceed:
  *		MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ
@@ -673,10 +673,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+			return NULL;
 		}
 		block->aset = set;
 		block->freeptr = block->endptr = ((char *) block) + blksize;
@@ -867,10 +864,7 @@ AllocSetAlloc(MemoryContext context, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+			return NULL;
 		}
 
 		block->aset = set;
@@ -1002,9 +996,10 @@ AllocSetFree(MemoryContext context, void *pointer)
 
 /*
  * AllocSetRealloc
- *		Returns new pointer to allocated memory of given size; this memory
- *		is added to the set.  Memory associated with given pointer is copied
- *		into the new memory, and the old memory is freed.
+ *		Returns new pointer to allocated memory of given size or NULL if
+ *		request could not be completed; this memory is added to the set.
+ *		Memory associated with given pointer is copied into the new memory,
+ *		and the old memory is freed.
  *
  * Without MEMORY_CONTEXT_CHECKING, we don't know the old request size.  This
  * makes our Valgrind client requests less-precise, hazarding false negatives.
@@ -1109,10 +1104,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 		if (block == NULL)
 		{
 			MemoryContextStats(TopMemoryContext);
-			ereport(ERROR,
-					(errcode(ERRCODE_OUT_OF_MEMORY),
-					 errmsg("out of memory"),
-					 errdetail("Failed on request of size %zu.", size)));
+			return NULL;
 		}
 		block->freeptr = block->endptr = ((char *) block) + blksize;
 
@@ -1179,6 +1171,10 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
 		/* allocate new chunk */
 		newPointer = AllocSetAlloc((MemoryContext) set, size);
 
+		/* leave immediately if request was not completed */
+		if (newPointer == NULL)
+			return NULL;
+
 		/*
 		 * AllocSetAlloc() just made the region NOACCESS.  Change it to
 		 * UNDEFINED for the moment; memcpy() will then transfer definedness
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index aa0d458..f6cf0cc 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -623,6 +623,12 @@ MemoryContextAlloc(MemoryContext context, Size size)
 	context->isReset = false;
 
 	ret = (*context->methods->alloc) (context, size);
+	if (ret == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
 	return ret;
@@ -649,6 +655,12 @@ MemoryContextAllocZero(MemoryContext context, Size size)
 	context->isReset = false;
 
 	ret = (*context->methods->alloc) (context, size);
+	if (ret == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
 	MemSetAligned(ret, 0, size);
@@ -677,6 +689,12 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size)
 	context->isReset = false;
 
 	ret = (*context->methods->alloc) (context, size);
+	if (ret == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
 	MemSetLoop(ret, 0, size);
@@ -699,6 +717,12 @@ palloc(Size size)
 	CurrentMemoryContext->isReset = false;
 
 	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
+	if (ret == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+
 	VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
 
 	return ret;
@@ -719,6 +743,12 @@ palloc0(Size size)
 	CurrentMemoryContext->isReset = false;
 
 	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
+	if (ret == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+
 	VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
 
 	MemSetAligned(ret, 0, size);
@@ -789,6 +819,12 @@ repalloc(void *pointer, Size size)
 	Assert(!context->isReset);
 
 	ret = (*context->methods->realloc) (context, pointer, size);
+	if (ret == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+
 	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
 
 	return ret;
@@ -814,6 +850,12 @@ MemoryContextAllocHuge(MemoryContext context, Size size)
 	context->isReset = false;
 
 	ret = (*context->methods->alloc) (context, size);
+	if (ret == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
 	return ret;
@@ -854,6 +896,12 @@ repalloc_huge(void *pointer, Size size)
 	Assert(!context->isReset);
 
 	ret = (*context->methods->realloc) (context, pointer, size);
+	if (ret == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+
 	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
 
 	return ret;
-- 
2.2.2

0002-Add-_noerror-routines-for-palloc-memory-allocation.patchtext/x-patch; charset=US-ASCII; name=0002-Add-_noerror-routines-for-palloc-memory-allocation.patchDownload
From 13a05f5b28ab00864a9d96fae5a72402cf43184b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 28 Jan 2015 22:46:04 +0900
Subject: [PATCH 2/3] Add *_noerror routines for palloc memory allocation

The following routines are added for frontend and backend:
- palloc_noerror, equivalent of palloc but returning NULL if memory request
could not be completed
- palloc0_noerror, equivalent of palloc0 but returning NULL if memory
request could not be completed
- repalloc_noerror, equivalent of repalloc bur returning NULL if memory
request could not be completed
---
 src/backend/utils/mmgr/mcxt.c    | 129 ++++++++++++++++++++++++---------------
 src/common/fe_memutils.c         |  73 ++++++++++++++++------
 src/include/common/fe_memutils.h |   3 +
 src/include/utils/palloc.h       |   3 +
 4 files changed, 140 insertions(+), 68 deletions(-)

diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index f6cf0cc..f8907a8 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -56,6 +56,10 @@ MemoryContext PortalContext = NULL;
 
 static void MemoryContextStatsInternal(MemoryContext context, int level);
 
+/* wrapper routines for allocation */
+static inline void* palloc_internal(Size size, bool noerror);
+static inline void* repalloc_internal(void *pointer, Size size, bool noerror);
+
 /*
  * You should not do memory allocations within a critical section, because
  * an out-of-memory error will be escalated to a PANIC. To enforce that
@@ -702,8 +706,8 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size)
 	return ret;
 }
 
-void *
-palloc(Size size)
+static inline void *
+palloc_internal(Size size, bool noerror)
 {
 	/* duplicates MemoryContextAlloc to avoid increased overhead */
 	void	   *ret;
@@ -717,42 +721,90 @@ palloc(Size size)
 	CurrentMemoryContext->isReset = false;
 
 	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
-	if (ret == NULL)
+	if (!noerror && ret == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("out of memory"),
 				 errdetail("Failed on request of size %zu.", size)));
 
-	VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
+	if (ret != NULL)
+		VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
 
 	return ret;
 }
 
-void *
-palloc0(Size size)
+static inline void *
+repalloc_internal(void *pointer, Size size, bool noerror)
 {
-	/* duplicates MemoryContextAllocZero to avoid increased overhead */
+	MemoryContext context;
 	void	   *ret;
 
-	AssertArg(MemoryContextIsValid(CurrentMemoryContext));
-	AssertNotInCriticalSection(CurrentMemoryContext);
-
 	if (!AllocSizeIsValid(size))
 		elog(ERROR, "invalid memory alloc request size %zu", size);
 
-	CurrentMemoryContext->isReset = false;
+	/*
+	 * Try to detect bogus pointers handed to us, poorly though we can.
+	 * Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
+	 * allocated chunk.
+	 */
+	Assert(pointer != NULL);
+	Assert(pointer == (void *) MAXALIGN(pointer));
 
-	ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
-	if (ret == NULL)
+	/*
+	 * OK, it's probably safe to look at the chunk header.
+	 */
+	context = ((StandardChunkHeader *)
+			   ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context;
+
+	AssertArg(MemoryContextIsValid(context));
+	AssertNotInCriticalSection(context);
+
+	/* isReset must be false already */
+	Assert(!context->isReset);
+
+	ret = (*context->methods->realloc) (context, pointer, size);
+	if (!noerror && ret == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("out of memory"),
 				 errdetail("Failed on request of size %zu.", size)));
 
-	VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
+	if (ret != NULL)
+		VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+
+	return ret;
+
+}
+
+void *
+palloc(Size size)
+{
+	return palloc_internal(size, false);
+}
+
+void *
+palloc_noerror(Size size)
+{
+	return palloc_internal(size, true);
+}
+
+void *
+palloc0(Size size)
+{
+	void	   *ret;
 
+	ret = palloc_internal(size, false);
 	MemSetAligned(ret, 0, size);
+	return ret;
+}
 
+void *
+palloc0_noerror(Size size)
+{
+	void	   *ret;
+
+	ret = palloc_internal(size, true);
+	MemSetAligned(ret, 0, size);
 	return ret;
 }
 
@@ -787,47 +839,24 @@ pfree(void *pointer)
 
 /*
  * repalloc
- *		Adjust the size of a previously allocated chunk.
+ *		Adjust the size of a previously allocated chunk, failing if
+ *		request could not be completed.
  */
 void *
 repalloc(void *pointer, Size size)
 {
-	MemoryContext context;
-	void	   *ret;
-
-	if (!AllocSizeIsValid(size))
-		elog(ERROR, "invalid memory alloc request size %zu", size);
-
-	/*
-	 * Try to detect bogus pointers handed to us, poorly though we can.
-	 * Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
-	 * allocated chunk.
-	 */
-	Assert(pointer != NULL);
-	Assert(pointer == (void *) MAXALIGN(pointer));
-
-	/*
-	 * OK, it's probably safe to look at the chunk header.
-	 */
-	context = ((StandardChunkHeader *)
-			   ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context;
-
-	AssertArg(MemoryContextIsValid(context));
-	AssertNotInCriticalSection(context);
-
-	/* isReset must be false already */
-	Assert(!context->isReset);
-
-	ret = (*context->methods->realloc) (context, pointer, size);
-	if (ret == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("out of memory"),
-				 errdetail("Failed on request of size %zu.", size)));
-
-	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+	return repalloc_internal(pointer, size, false);
+}
 
-	return ret;
+/*
+ * repalloc_noerror
+ *		Adjust the size of a previously allocated chunk, returning NULL
+ *		if request could not be completed.
+ */
+void *
+repalloc_noerror(void *pointer, Size size)
+{
+	return repalloc_internal(pointer, size, true);
 }
 
 /*
diff --git a/src/common/fe_memutils.c b/src/common/fe_memutils.c
index 345221e..9a312c4 100644
--- a/src/common/fe_memutils.c
+++ b/src/common/fe_memutils.c
@@ -19,8 +19,8 @@
 
 #include "postgres_fe.h"
 
-void *
-pg_malloc(size_t size)
+static inline void *
+pg_malloc_internal(size_t size, bool noerror)
 {
 	void	   *tmp;
 
@@ -28,7 +28,24 @@ pg_malloc(size_t size)
 	if (size == 0)
 		size = 1;
 	tmp = malloc(size);
-	if (!tmp)
+	if (!tmp && !noerror)
+	{
+		fprintf(stderr, _("out of memory\n"));
+		exit(EXIT_FAILURE);
+	}
+	return tmp;
+}
+
+static inline void *
+pg_realloc_internal(void *ptr, size_t size, bool noerror)
+{
+	void	   *tmp;
+
+	/* Avoid unportable behavior of realloc(NULL, 0) */
+	if (ptr == NULL && size == 0)
+		size = 1;
+	tmp = realloc(ptr, size);
+	if (!tmp && !noerror)
 	{
 		fprintf(stderr, _("out of memory\n"));
 		exit(EXIT_FAILURE);
@@ -37,6 +54,12 @@ pg_malloc(size_t size)
 }
 
 void *
+pg_malloc(size_t size)
+{
+	return pg_malloc_internal(size, false);
+}
+
+void *
 pg_malloc0(size_t size)
 {
 	void	   *tmp;
@@ -49,20 +72,10 @@ pg_malloc0(size_t size)
 void *
 pg_realloc(void *ptr, size_t size)
 {
-	void	   *tmp;
-
-	/* Avoid unportable behavior of realloc(NULL, 0) */
-	if (ptr == NULL && size == 0)
-		size = 1;
-	tmp = realloc(ptr, size);
-	if (!tmp)
-	{
-		fprintf(stderr, _("out of memory\n"));
-		exit(EXIT_FAILURE);
-	}
-	return tmp;
+	return pg_realloc_internal(ptr, size, false);
 }
 
+
 /*
  * "Safe" wrapper around strdup().
  */
@@ -100,13 +113,31 @@ pg_free(void *ptr)
 void *
 palloc(Size size)
 {
-	return pg_malloc(size);
+	return pg_malloc_internal(size, false);
+}
+
+void *
+palloc_noerror(Size size)
+{
+	return pg_malloc_internal(size, true);
 }
 
 void *
 palloc0(Size size)
 {
-	return pg_malloc0(size);
+	void	   *tmp;
+	tmp = pg_malloc_internal(size, false);
+	MemSet(tmp, 0, size);
+	return tmp;
+}
+
+void *
+palloc0_noerror(Size size)
+{
+	void	   *tmp;
+	tmp = pg_malloc_internal(size, true);
+	MemSet(tmp, 0, size);
+	return tmp;
 }
 
 void
@@ -124,5 +155,11 @@ pstrdup(const char *in)
 void *
 repalloc(void *pointer, Size size)
 {
-	return pg_realloc(pointer, size);
+	return pg_realloc_internal(pointer, size, false);
+}
+
+void *
+repalloc_noerror(void *pointer, Size size)
+{
+	return pg_realloc_internal(pointer, size, true);
 }
diff --git a/src/include/common/fe_memutils.h b/src/include/common/fe_memutils.h
index f7114c2..0151e6e 100644
--- a/src/include/common/fe_memutils.h
+++ b/src/include/common/fe_memutils.h
@@ -19,8 +19,11 @@ extern void pg_free(void *pointer);
 /* Equivalent functions, deliberately named the same as backend functions */
 extern char *pstrdup(const char *in);
 extern void *palloc(Size size);
+extern void *palloc_noerror(Size size);
 extern void *palloc0(Size size);
+extern void *palloc0_noerror(Size size);
 extern void *repalloc(void *pointer, Size size);
+extern void *repalloc_noerror(void *pointer, Size size);
 extern void pfree(void *pointer);
 
 /* sprintf into a palloc'd buffer --- these are in psprintf.c */
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index ca03f2b..3634a7f 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -50,8 +50,11 @@ extern void *MemoryContextAllocZero(MemoryContext context, Size size);
 extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
 
 extern void *palloc(Size size);
+extern void *palloc_noerror(Size size);
 extern void *palloc0(Size size);
+extern void *palloc0_noerror(Size size);
 extern void *repalloc(void *pointer, Size size);
+extern void *repalloc_noerror(void *pointer, Size size);
 extern void pfree(void *pointer);
 
 /*
-- 
2.2.2

0003-Create-MemoryContextAllocExtended-central-routine-fo.patchtext/x-patch; charset=US-ASCII; name=0003-Create-MemoryContextAllocExtended-central-routine-fo.patchDownload
From 1ff0406b11a6e54cdb0a4312c199dbe7800fea82 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 28 Jan 2015 23:27:54 +0900
Subject: [PATCH 3/3] Create MemoryContextAllocExtended central routine for
 memory allocation

This new routine is the central point of all the MemoryContextAlloc*
functions controlled by a set of flags allowing a far wider control
of how allocation can be done in a memory context.
---
 src/backend/utils/mmgr/mcxt.c | 107 ++++++++++++------------------------------
 src/include/utils/palloc.h    |  11 +++++
 2 files changed, 41 insertions(+), 77 deletions(-)

diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index f8907a8..01201fb 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -607,21 +607,20 @@ MemoryContextCreate(NodeTag tag, Size size,
 }
 
 /*
- * MemoryContextAlloc
- *		Allocate space within the specified context.
- *
- * This could be turned into a macro, but we'd have to import
- * nodes/memnodes.h into postgres.h which seems a bad idea.
+ * MemoryContextAllocExtended
+ *		Allocation space within the specified context using flag options
+ *		specified by caller.
  */
 void *
-MemoryContextAlloc(MemoryContext context, Size size)
+MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 {
 	void	   *ret;
 
 	AssertArg(MemoryContextIsValid(context));
 	AssertNotInCriticalSection(context);
 
-	if (!AllocSizeIsValid(size))
+	if (((flags & ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(size)) ||
+		!AllocSizeIsValid(size))
 		elog(ERROR, "invalid memory alloc request size %zu", size);
 
 	context->isReset = false;
@@ -635,75 +634,48 @@ MemoryContextAlloc(MemoryContext context, Size size)
 
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
+	/*
+	 * MemSetAligned and MemSetLoop should not be called in parallel
+	 * (see c.h for more details).
+	 */
+	Assert((flags & ALLOC_ALIGNED) == 0 || (flags & ALLOC_HUGE) == 0);
+
+	if ((flags & ALLOC_ZERO) != 0)
+		MemSetAligned(ret, 0, size);
+	if ((flags & ALLOC_ALIGNED) != 0)
+		MemSetLoop(ret, 0, size);
+
 	return ret;
 }
 
 /*
+ * MemoryContextAlloc
+ *		Allocate space within the specified context.
+ */
+void *
+MemoryContextAlloc(MemoryContext context, Size size)
+{
+	return MemoryContextAllocExtended(context, size, 0);
+}
+
+/*
  * MemoryContextAllocZero
  *		Like MemoryContextAlloc, but clears allocated memory
- *
- *	We could just call MemoryContextAlloc then clear the memory, but this
- *	is a very common combination, so we provide the combined operation.
  */
 void *
 MemoryContextAllocZero(MemoryContext context, Size size)
 {
-	void	   *ret;
-
-	AssertArg(MemoryContextIsValid(context));
-	AssertNotInCriticalSection(context);
-
-	if (!AllocSizeIsValid(size))
-		elog(ERROR, "invalid memory alloc request size %zu", size);
-
-	context->isReset = false;
-
-	ret = (*context->methods->alloc) (context, size);
-	if (ret == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("out of memory"),
-				 errdetail("Failed on request of size %zu.", size)));
-
-	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
-
-	MemSetAligned(ret, 0, size);
-
-	return ret;
+	return MemoryContextAllocExtended(context, size, ALLOC_ZERO);
 }
 
 /*
  * MemoryContextAllocZeroAligned
  *		MemoryContextAllocZero where length is suitable for MemSetLoop
- *
- *	This might seem overly specialized, but it's not because newNode()
- *	is so often called with compile-time-constant sizes.
  */
 void *
 MemoryContextAllocZeroAligned(MemoryContext context, Size size)
 {
-	void	   *ret;
-
-	AssertArg(MemoryContextIsValid(context));
-	AssertNotInCriticalSection(context);
-
-	if (!AllocSizeIsValid(size))
-		elog(ERROR, "invalid memory alloc request size %zu", size);
-
-	context->isReset = false;
-
-	ret = (*context->methods->alloc) (context, size);
-	if (ret == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("out of memory"),
-				 errdetail("Failed on request of size %zu.", size)));
-
-	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
-
-	MemSetLoop(ret, 0, size);
-
-	return ret;
+	return MemoryContextAllocExtended(context, size, ALLOC_ALIGNED);
 }
 
 static inline void *
@@ -868,26 +840,7 @@ repalloc_noerror(void *pointer, Size size)
 void *
 MemoryContextAllocHuge(MemoryContext context, Size size)
 {
-	void	   *ret;
-
-	AssertArg(MemoryContextIsValid(context));
-	AssertNotInCriticalSection(context);
-
-	if (!AllocHugeSizeIsValid(size))
-		elog(ERROR, "invalid memory alloc request size %zu", size);
-
-	context->isReset = false;
-
-	ret = (*context->methods->alloc) (context, size);
-	if (ret == NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("out of memory"),
-				 errdetail("Failed on request of size %zu.", size)));
-
-	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
-
-	return ret;
+	return MemoryContextAllocExtended(context, size, ALLOC_HUGE);
 }
 
 /*
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index 3634a7f..a8bf9a1 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -43,8 +43,19 @@ typedef struct MemoryContextData *MemoryContext;
 extern PGDLLIMPORT MemoryContext CurrentMemoryContext;
 
 /*
+ * Additional options for MemoryContextAllocExtended()
+ */
+#define ALLOC_HUGE		0x01	/* huge allocation */
+#define ALLOC_ZERO		0x02	/* clear allocated memory */
+#define ALLOC_NO_OOM	0x04	/* no failure if out-of-memory */
+#define ALLOC_ALIGNED	0x08	/* request length suitable for MemSetLoop */
+
+/*
  * Fundamental memory-allocation operations (more are in utils/memutils.h)
  */
+extern void *MemoryContextAllocExtended(MemoryContext context,
+										Size size,
+										int flags);
 extern void *MemoryContextAlloc(MemoryContext context, Size size);
 extern void *MemoryContextAllocZero(MemoryContext context, Size size);
 extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
-- 
2.2.2

#27Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#26)
Re: Safe memory allocation functions

On Wed, Jan 28, 2015 at 9:34 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

As a result of all the comments on this thread, here are 3 patches
implementing incrementally the different ideas from everybody:
1) 0001 modifies aset.c to return unconditionally NULL in case of an
OOM instead of reporting an error. All the OOM error reports are moved
to mcxt.c (MemoryContextAlloc* and palloc*)

This seems like a good idea, but I think it's pretty clear that the
MemoryContextStats(TopMemoryContext) calls ought to move along with
the OOM error report. The stats are basically another kind of
error-case output, and the whole point here is that the caller wants
to have control of what happens when malloc fails. Committed that
way.

2) 0002 adds the noerror routines for frontend and backend.

We don't have consensus on this name; as I read it, Andres and I are
both strongly opposed to it. Instead of continuing to litigate that
point, I'd like to propose that we just leave this out. We are
unlikely to have so many callers who need the no-oom-error behavior to
justify adding a bunch of convenience functions --- and if that does
happen, we can resume arguing about the naming then. For now, let's
just say that if you want that behavior, you should use
MemoryContextAllocExtended(CurrentMemoryContext, ...).

3) 0003 adds MemoryContextAllocExtended that can be called with the
following control flags:
#define ALLOC_HUGE 0x01 /* huge allocation */
#define ALLOC_ZERO 0x02 /* clear allocated memory */
#define ALLOC_NO_OOM 0x04 /* no failure if out-of-memory */
#define ALLOC_ALIGNED 0x08 /* request length suitable for MemSetLoop */
This groups MemoryContextAlloc, MemoryContextAllocHuge,
MemoryContextAllocZero and MemoryContextAllocZeroAligned under the
same central routine.

I recommend we leave the existing MemoryContextAlloc* functions alone
and add a new MemoryContextAllocExtended() function *in addition*. I
think the reason we have multiple copies of this code is because they
are sufficiently hot to make the effect of a few extra CPU
instructions potentially material. By having separate copies of the
code, we avoid introducing extra branches.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#27)
1 attachment(s)
Re: Safe memory allocation functions

On Fri, Jan 30, 2015 at 12:29 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Jan 28, 2015 at 9:34 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

As a result of all the comments on this thread, here are 3 patches
implementing incrementally the different ideas from everybody:
1) 0001 modifies aset.c to return unconditionally NULL in case of an
OOM instead of reporting an error. All the OOM error reports are moved
to mcxt.c (MemoryContextAlloc* and palloc*)

This seems like a good idea, but I think it's pretty clear that the
MemoryContextStats(TopMemoryContext) calls ought to move along with
the OOM error report. The stats are basically another kind of
error-case output, and the whole point here is that the caller wants
to have control of what happens when malloc fails. Committed that
way.

Thanks for the clarifications!

2) 0002 adds the noerror routines for frontend and backend.

We don't have consensus on this name; as I read it, Andres and I are
both strongly opposed to it. Instead of continuing to litigate that
point, I'd like to propose that we just leave this out. We are
unlikely to have so many callers who need the no-oom-error behavior to
justify adding a bunch of convenience functions --- and if that does
happen, we can resume arguing about the naming then. For now, let's
just say that if you want that behavior, you should use
MemoryContextAllocExtended(CurrentMemoryContext, ...).

Fine for me, any extension or module can still define their own
equivalent of palloc_noerror or whatever using the Extended interface.

3) 0003 adds MemoryContextAllocExtended

I recommend we leave the existing MemoryContextAlloc* functions alone
and add a new MemoryContextAllocExtended() function *in addition*. I
think the reason we have multiple copies of this code is because they
are sufficiently hot to make the effect of a few extra CPU
instructions potentially material. By having separate copies of the
code, we avoid introducing extra branches.

Yes, this refactoring was good for testing actually... I have changed
the flags as follows, appending MCXT_ seemed cleaner after waking up
this morning:
+#define MCXT_ALLOC_HUGE                   0x01    /* huge allocation */
+#define MCXT_ALLOC_NO_OOM              0x02    /* no failure if
out-of-memory */
+#define MCXT_ALLOC_ZERO                    0x04    /* clear allocated
memory using
+
          * MemSetAligned */
+#define MCXT_ALLOC_ZERO_ALIGNED   0x08    /* clear allocated memory using
+
          * MemSetLoop */
-- 
Michael

Attachments:

0001-Create-MemoryContextAllocExtended-central-routine-fo.patchtext/x-diff; charset=US-ASCII; name=0001-Create-MemoryContextAllocExtended-central-routine-fo.patchDownload
From b5d43fd598e177c402c354f0b76aca52305463c6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 30 Jan 2015 12:56:21 +0900
Subject: [PATCH] Create MemoryContextAllocExtended central routine for memory
 allocation

This new routine is the central point can be used by extensions and
third-part utilities in a more extensive way than the already present
routines MemoryContextAlloc, one of the control flags introduced being
particularly useful to avoid out-of-memory errors when allocation request
cannot be completed correctly.
---
 src/backend/utils/mmgr/mcxt.c | 57 +++++++++++++++++++++++++++++++++++++++++++
 src/include/utils/palloc.h    | 12 +++++++++
 2 files changed, 69 insertions(+)

diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index c62922a..26579e3 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -603,6 +603,63 @@ MemoryContextCreate(NodeTag tag, Size size,
 }
 
 /*
+ * MemoryContextAllocExtended
+ *		Allocate space within the specified context using flag options
+ *		defined by caller.
+ *
+ * The following control flags can be used:
+ * - MCXT_ALLOC_HUGE, allocate possibly-expansive space. this is
+ *   equivalent to MemoryContextAllocHuge.
+ * - MCXT_ALLOC_NO_OOM, not fail in case of allocation request
+ *   failure and return NULL.
+ * - MCXT_ALLOC_ZERO, clear allocated memory using MemSetAligned.
+ * - MCXT_ALLOC_ZERO_ALIGNED, clear memory using MemSetLoop.
+ */
+void *
+MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
+{
+	void	   *ret;
+
+	AssertArg(MemoryContextIsValid(context));
+	AssertNotInCriticalSection(context);
+
+	if (((flags & MCXT_ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(size)) ||
+		!AllocSizeIsValid(size))
+		elog(ERROR, "invalid memory alloc request size %zu", size);
+
+	context->isReset = false;
+
+	ret = (*context->methods->alloc) (context, size);
+	if ((flags & MCXT_ALLOC_NO_OOM) == 0 && ret == NULL)
+	{
+		MemoryContextStats(TopMemoryContext);
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+	}
+
+	if (ret == NULL)
+		return NULL;
+
+	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+
+	/*
+	 * MemSetAligned and MemSetLoop should not be called in the same
+	 * context (see c.h for more details).
+	 */
+	Assert((flags & MCXT_ALLOC_ZERO) == 0 ||
+		   (flags & MCXT_ALLOC_ZERO_ALIGNED) == 0);
+
+	if ((flags & MCXT_ALLOC_ZERO) != 0)
+		MemSetAligned(ret, 0, size);
+	if ((flags & MCXT_ALLOC_ZERO_ALIGNED) != 0)
+		MemSetLoop(ret, 0, size);
+
+	return ret;
+}
+
+/*
  * MemoryContextAlloc
  *		Allocate space within the specified context.
  *
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index ca03f2b..34acabe 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -43,8 +43,20 @@ typedef struct MemoryContextData *MemoryContext;
 extern PGDLLIMPORT MemoryContext CurrentMemoryContext;
 
 /*
+ * Control flags for options of MemoryContextAllocExtended()
+ */
+#define MCXT_ALLOC_HUGE			0x01	/* huge allocation */
+#define MCXT_ALLOC_NO_OOM		0x02	/* no failure if out-of-memory */
+#define MCXT_ALLOC_ZERO			0x04	/* clear allocated memory using
+										 * MemSetAligned */
+#define MCXT_ALLOC_ZERO_ALIGNED	0x08	/* clear allocated memory using
+										 * MemSetLoop */
+
+/*
  * Fundamental memory-allocation operations (more are in utils/memutils.h)
  */
+extern void *MemoryContextAllocExtended(MemoryContext context,
+										Size size, int flags);
 extern void *MemoryContextAlloc(MemoryContext context, Size size);
 extern void *MemoryContextAllocZero(MemoryContext context, Size size);
 extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
-- 
2.2.2

#29Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#28)
3 attachment(s)
Re: Safe memory allocation functions

I wrote:

Yes, this refactoring was good for testing actually...

Oops, I have been too hasty when sending previous patch, there was a
bug related to huge allocations. Patch correcting this bug is
attached.

Attached are as well two things I have used to test the new API:
- A hack refactoring the existing routines MemoryContextAlloc* to use
the extended API
- An extension with a function doing a direct call to the extended API
able to control the flags used:
CREATE FUNCTION blackhole_palloc(size bigint,
is_huge bool,
is_no_oom bool,
is_zero bool,
is_zero_aligned bool)

Here are some tests done on a small box of 384MB with direct calls of
the extended API:
=# create extension blackhole ;
CREATE EXTENSION
-- failure for normal allocation because size >= 1GB
=# select blackhole_palloc(1024 * 1024 * 1024, false, false, false, false);
ERROR: XX000: invalid memory alloc request size 1073741824
LOCATION: MemoryContextAllocExtended, mcxt.c:628
-- Failure of OOM because normal allocation can be done, but no memory
=# select blackhole_palloc(1024 * 1024 * 1024 - 1, false, false, false, false);
ERROR: 53200: out of memory
DETAIL: Failed on request of size 1073741823.
LOCATION: MemoryContextAllocExtended, mcxt.c:639
-- No failure, bypassing OOM error
=# select blackhole_palloc(1024 * 1024 * 1024 - 1, false, true, false, false);
blackhole_palloc
------------------
null
(1 row)
-- Huge allocation, no error because OOM error is bypassed
=# select blackhole_palloc(1024 * 1024 * 1024, true, true, false, false);
blackhole_palloc
------------------
null
(1 row)
-- OOM error, huge allocation failure
=# select blackhole_palloc(1024 * 1024 * 1024, true, false, false, false);
ERROR: 53200: out of memory
DETAIL: Failed on request of size 1073741824.
LOCATION: MemoryContextAllocExtended, mcxt.c:639
-- Assertion failure, zero and zero aligned cannot be called at the same time
=# select blackhole_palloc(1024 * 1024, false, false, true, true);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
--
Michael

Attachments:

0001-Create-MemoryContextAllocExtended-central-routine-fo.patchtext/x-diff; charset=US-ASCII; name=0001-Create-MemoryContextAllocExtended-central-routine-fo.patchDownload
From 09bf9364a80dfe6426c91bdefe6ae2135e6f20c3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 30 Jan 2015 12:56:21 +0900
Subject: [PATCH 1/2] Create MemoryContextAllocExtended central routine for
 memory allocation

This new routine is the central point can be used by extensions and
third-part utilities in a more extensive way than the already present
routines MemoryContextAlloc, one of the control flags introduced being
particularly useful to avoid out-of-memory errors when allocation request
cannot be completed correctly.
---
 src/backend/utils/mmgr/mcxt.c | 57 +++++++++++++++++++++++++++++++++++++++++++
 src/include/utils/palloc.h    | 12 +++++++++
 2 files changed, 69 insertions(+)

diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index c62922a..fce8a0e 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -603,6 +603,63 @@ MemoryContextCreate(NodeTag tag, Size size,
 }
 
 /*
+ * MemoryContextAllocExtended
+ *		Allocate space within the specified context using flag options
+ *		defined by caller.
+ *
+ * The following control flags can be used:
+ * - MCXT_ALLOC_HUGE, allocate possibly-expansive space. this is
+ *   equivalent to MemoryContextAllocHuge.
+ * - MCXT_ALLOC_NO_OOM, not fail in case of allocation request
+ *   failure and return NULL.
+ * - MCXT_ALLOC_ZERO, clear allocated memory using MemSetAligned.
+ * - MCXT_ALLOC_ZERO_ALIGNED, clear memory using MemSetLoop.
+ */
+void *
+MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
+{
+	void	   *ret;
+
+	AssertArg(MemoryContextIsValid(context));
+	AssertNotInCriticalSection(context);
+
+	if (((flags & MCXT_ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(size)) ||
+		((flags & MCXT_ALLOC_HUGE) == 0 && !AllocSizeIsValid(size)))
+		elog(ERROR, "invalid memory alloc request size %zu", size);
+
+	context->isReset = false;
+
+	ret = (*context->methods->alloc) (context, size);
+	if ((flags & MCXT_ALLOC_NO_OOM) == 0 && ret == NULL)
+	{
+		MemoryContextStats(TopMemoryContext);
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of memory"),
+				 errdetail("Failed on request of size %zu.", size)));
+	}
+
+	if (ret == NULL)
+		return NULL;
+
+	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
+
+	/*
+	 * MemSetAligned and MemSetLoop should not be called in the same
+	 * context (see c.h for more details).
+	 */
+	Assert((flags & MCXT_ALLOC_ZERO) == 0 ||
+		   (flags & MCXT_ALLOC_ZERO_ALIGNED) == 0);
+
+	if ((flags & MCXT_ALLOC_ZERO) != 0)
+		MemSetAligned(ret, 0, size);
+	if ((flags & MCXT_ALLOC_ZERO_ALIGNED) != 0)
+		MemSetLoop(ret, 0, size);
+
+	return ret;
+}
+
+/*
  * MemoryContextAlloc
  *		Allocate space within the specified context.
  *
diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index ca03f2b..34acabe 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -43,8 +43,20 @@ typedef struct MemoryContextData *MemoryContext;
 extern PGDLLIMPORT MemoryContext CurrentMemoryContext;
 
 /*
+ * Control flags for options of MemoryContextAllocExtended()
+ */
+#define MCXT_ALLOC_HUGE			0x01	/* huge allocation */
+#define MCXT_ALLOC_NO_OOM		0x02	/* no failure if out-of-memory */
+#define MCXT_ALLOC_ZERO			0x04	/* clear allocated memory using
+										 * MemSetAligned */
+#define MCXT_ALLOC_ZERO_ALIGNED	0x08	/* clear allocated memory using
+										 * MemSetLoop */
+
+/*
  * Fundamental memory-allocation operations (more are in utils/memutils.h)
  */
+extern void *MemoryContextAllocExtended(MemoryContext context,
+										Size size, int flags);
 extern void *MemoryContextAlloc(MemoryContext context, Size size);
 extern void *MemoryContextAllocZero(MemoryContext context, Size size);
 extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
-- 
2.2.2

0002-Small-hack-to-test-extended-function-for-mcxt.patchtext/x-diff; charset=US-ASCII; name=0002-Small-hack-to-test-extended-function-for-mcxt.patchDownload
From 88f3fb0129a6fad35114902320b006183ea6da5f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 30 Jan 2015 13:14:23 +0900
Subject: [PATCH 2/2] Small hack to test extended function for mcxt

---
 src/backend/utils/mmgr/mcxt.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index fce8a0e..efa81fc 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -669,6 +669,8 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 void *
 MemoryContextAlloc(MemoryContext context, Size size)
 {
+	return MemoryContextAllocExtended(context, size, 0);
+#ifdef NOT_USED
 	void	   *ret;
 
 	AssertArg(MemoryContextIsValid(context));
@@ -692,6 +694,7 @@ MemoryContextAlloc(MemoryContext context, Size size)
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
 	return ret;
+#endif
 }
 
 /*
@@ -704,6 +707,8 @@ MemoryContextAlloc(MemoryContext context, Size size)
 void *
 MemoryContextAllocZero(MemoryContext context, Size size)
 {
+	return MemoryContextAllocExtended(context, size, MCXT_ALLOC_ZERO);
+#ifdef NOT_USED
 	void	   *ret;
 
 	AssertArg(MemoryContextIsValid(context));
@@ -729,6 +734,7 @@ MemoryContextAllocZero(MemoryContext context, Size size)
 	MemSetAligned(ret, 0, size);
 
 	return ret;
+#endif
 }
 
 /*
@@ -741,6 +747,8 @@ MemoryContextAllocZero(MemoryContext context, Size size)
 void *
 MemoryContextAllocZeroAligned(MemoryContext context, Size size)
 {
+	return MemoryContextAllocExtended(context, size, MCXT_ALLOC_ZERO_ALIGNED);
+#ifdef NOT_USED
 	void	   *ret;
 
 	AssertArg(MemoryContextIsValid(context));
@@ -766,6 +774,7 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size)
 	MemSetLoop(ret, 0, size);
 
 	return ret;
+#endif
 }
 
 void *
@@ -911,6 +920,8 @@ repalloc(void *pointer, Size size)
 void *
 MemoryContextAllocHuge(MemoryContext context, Size size)
 {
+	return MemoryContextAllocExtended(context, size, MCXT_ALLOC_HUGE);
+#ifdef NOT_USED
 	void	   *ret;
 
 	AssertArg(MemoryContextIsValid(context));
@@ -934,6 +945,7 @@ MemoryContextAllocHuge(MemoryContext context, Size size)
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
 	return ret;
+#endif
 }
 
 /*
-- 
2.2.2

blackhole.tar.gzapplication/x-gzip; name=blackhole.tar.gzDownload
#30Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#29)
Re: Safe memory allocation functions

On Fri, Jan 30, 2015 at 1:10 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

I wrote:

Yes, this refactoring was good for testing actually...

Oops, I have been too hasty when sending previous patch, there was a
bug related to huge allocations. Patch correcting this bug is
attached.

Committed. I didn't think we really need to expose two separate flags
for the aligned and unaligned cases, so I ripped that out. I also
removed the duplicate documentation of the new constants in the
function header; having two copies of the documentation, one far
removed from the constants themselves, is a recipe for them eventually
getting out of sync. I also moved the function to what I thought was
a more logical place in the file, and rearranged the order of tests
slightly so that, in the common path, we test only whether ret == NULL
and not anything else.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#30)
Re: Safe memory allocation functions

On Sat, Jan 31, 2015 at 2:58 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Committed. I didn't think we really need to expose two separate flags
for the aligned and unaligned cases, so I ripped that out. I also
removed the duplicate documentation of the new constants in the
function header; having two copies of the documentation, one far
removed from the constants themselves, is a recipe for them eventually
getting out of sync. I also moved the function to what I thought was
a more logical place in the file, and rearranged the order of tests
slightly so that, in the common path, we test only whether ret == NULL
and not anything else.

Thanks a lot!

I have reworked a bit the module used for the tests of the new
extended routine (with new tests for Alloc, AllocZero and AllocHuge as
well) and pushed it here:
https://github.com/michaelpq/pg_plugins/tree/master/mcxtalloc_test
This is just to mention it for the sake of the archives, I cannot
really believe that it should be an in-core module in src/test/modules
as it does allocations of 1GB in environments where there is enough
memory. Note that it contains regression tests with alternate outputs
depending on the environment where they are run (still output may not
be stable depending on where those tests are run, particularly where
memory usage varies a lot).
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers