You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-07-15 01:04:37 +02:00
Add C memory contexts.
This commit is contained in:
@ -40,6 +40,10 @@
|
|||||||
<release-item>
|
<release-item>
|
||||||
<p>Page checksum module uses new C error handler.</p>
|
<p>Page checksum module uses new C error handler.</p>
|
||||||
</release-item>
|
</release-item>
|
||||||
|
|
||||||
|
<release-item>
|
||||||
|
<p>Add C memory contexts.</p>
|
||||||
|
</release-item>
|
||||||
</release-refactor-list>
|
</release-refactor-list>
|
||||||
</release-core-list>
|
</release-core-list>
|
||||||
|
|
||||||
|
395
src/common/memContext.c
Normal file
395
src/common/memContext.c
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory Context Manager
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "common/errorType.h"
|
||||||
|
#include "common/memContext.h"
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory context states
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef enum {memContextStateFree = 0, memContextStateFreeing, memContextStateActive} MemContextState;
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Contains information about the memory context
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
struct MemContext
|
||||||
|
{
|
||||||
|
MemContextState state; // Current state of the context
|
||||||
|
const char name[MEM_CONTEXT_NAME_SIZE + 1]; // Indicates what the context is being used for
|
||||||
|
|
||||||
|
MemContext *contextParent; // All contexts have a parent except top
|
||||||
|
|
||||||
|
MemContext **contextChildList; // List of contexts created in this context
|
||||||
|
int contextChildListSize; // Size of child context list (not the actual count of contexts)
|
||||||
|
|
||||||
|
void **allocList; // List of memory allocations created in this context
|
||||||
|
int allocListSize; // Size of alloc list (not the actual count of allocations)
|
||||||
|
|
||||||
|
MemContextCallback callbackFunction; // Function to call before the context is freed
|
||||||
|
void *callbackArgument; // Argument to pass to callback function
|
||||||
|
};
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Top context
|
||||||
|
|
||||||
|
The top context always exists and can never be freed. All other contexts are children of the top context. The top context is
|
||||||
|
generally used to allocate memory that exists for the life of the program.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext contextTop = {.state = memContextStateActive, .name = "TOP"};
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Current context
|
||||||
|
|
||||||
|
All memory allocations will be done from the current context. Initialized to top context at execution start.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext *contextCurrent = &contextTop;
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Wrapper around malloc()
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
static void *memAllocInternal(size_t size, bool zero)
|
||||||
|
{
|
||||||
|
// Allocate memory
|
||||||
|
void *buffer = malloc(size);
|
||||||
|
|
||||||
|
// Error when malloc fails
|
||||||
|
if (!buffer)
|
||||||
|
ERROR_THROW(MemoryError, "unable to allocate %lu bytes", size);
|
||||||
|
|
||||||
|
// Zero the memory when requested
|
||||||
|
if (zero)
|
||||||
|
memset(buffer, 0, size);
|
||||||
|
|
||||||
|
// Return the buffer
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Wrapper around realloc()
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
static void *memReAllocInternal(void *bufferOld, size_t sizeOld, size_t sizeNew, bool zeroNew)
|
||||||
|
{
|
||||||
|
// Allocate memory
|
||||||
|
void *bufferNew = realloc(bufferOld, sizeNew);
|
||||||
|
|
||||||
|
// Error when realloc fails
|
||||||
|
if(!bufferNew)
|
||||||
|
ERROR_THROW(MemoryError, "unable to reallocate %lu bytes", sizeNew);
|
||||||
|
|
||||||
|
// Zero the new memory when requested - old memory is left untouched else why bother with a realloc?
|
||||||
|
if (zeroNew)
|
||||||
|
memset(bufferNew + sizeOld, 0, sizeNew - sizeOld);
|
||||||
|
|
||||||
|
// Return the buffer
|
||||||
|
return bufferNew;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Wrapper around free()
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
static void memFreeInternal(void *buffer)
|
||||||
|
{
|
||||||
|
// Error if pointer is null
|
||||||
|
if(!buffer)
|
||||||
|
ERROR_THROW(MemoryError, "unable to free null pointer");
|
||||||
|
|
||||||
|
// Free the buffer
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Create a new memory context
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext *
|
||||||
|
memContextNew(const char *name)
|
||||||
|
{
|
||||||
|
// Check context name length
|
||||||
|
if (strlen(name) == 0 || strlen(name) > MEM_CONTEXT_NAME_SIZE)
|
||||||
|
ERROR_THROW(AssertError, "context name length must be > 0 and <= %d", MEM_CONTEXT_NAME_SIZE);
|
||||||
|
|
||||||
|
// Try to find space for the new context
|
||||||
|
int contextIdx;
|
||||||
|
|
||||||
|
for (contextIdx = 0; contextIdx < memContextCurrent()->contextChildListSize; contextIdx++)
|
||||||
|
if (!memContextCurrent()->contextChildList[contextIdx] ||
|
||||||
|
memContextCurrent()->contextChildList[contextIdx]->state == memContextStateFree)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no space was found then allocate more
|
||||||
|
if (contextIdx == memContextCurrent()->contextChildListSize)
|
||||||
|
{
|
||||||
|
// If no space has been allocated to the list
|
||||||
|
if (memContextCurrent()->contextChildListSize == 0)
|
||||||
|
{
|
||||||
|
// Allocate memory before modifying anything else in case there is an error
|
||||||
|
memContextCurrent()->contextChildList = memAllocInternal(sizeof(MemContext *) * MEM_CONTEXT_INITIAL_SIZE, true);
|
||||||
|
|
||||||
|
// Set new list size
|
||||||
|
memContextCurrent()->contextChildListSize = MEM_CONTEXT_INITIAL_SIZE;
|
||||||
|
}
|
||||||
|
// Else grow the list
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Calculate new list size
|
||||||
|
int contextChildListSizeNew = memContextCurrent()->contextChildListSize * 2;
|
||||||
|
|
||||||
|
// ReAllocate memory before modifying anything else in case there is an error
|
||||||
|
memContextCurrent()->contextChildList = memReAllocInternal(
|
||||||
|
memContextCurrent()->contextChildList, sizeof(MemContext *) * memContextCurrent()->contextChildListSize,
|
||||||
|
sizeof(MemContext *) * contextChildListSizeNew, true);
|
||||||
|
|
||||||
|
// Set new list size
|
||||||
|
memContextCurrent()->contextChildListSize = contextChildListSizeNew;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the context has not been allocated yet
|
||||||
|
if (!memContextCurrent()->contextChildList[contextIdx])
|
||||||
|
memContextCurrent()->contextChildList[contextIdx] = memAllocInternal(sizeof(MemContext), true);
|
||||||
|
|
||||||
|
// Get the context
|
||||||
|
MemContext *this = memContextCurrent()->contextChildList[contextIdx];
|
||||||
|
|
||||||
|
// Create initial space for allocations
|
||||||
|
this->allocList = memAllocInternal(sizeof(void *) * MEM_CONTEXT_ALLOC_INITIAL_SIZE, true);
|
||||||
|
this->allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE;
|
||||||
|
|
||||||
|
// Set the context name
|
||||||
|
strcpy((char *)this->name, name);
|
||||||
|
|
||||||
|
// Set new context active
|
||||||
|
this->state = memContextStateActive;
|
||||||
|
|
||||||
|
// Set current context as the parent
|
||||||
|
this->contextParent = memContextCurrent();
|
||||||
|
|
||||||
|
// Return context
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Register a callback to be called just before the context is freed
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
void
|
||||||
|
memContextCallback(MemContext *this, void (*callbackFunction)(void *), void *callbackArgument)
|
||||||
|
{
|
||||||
|
// Error if context is not active
|
||||||
|
if (this->state != memContextStateActive)
|
||||||
|
ERROR_THROW(AssertError, "cannot assign callback to inactive context");
|
||||||
|
|
||||||
|
// Top context cannot have a callback
|
||||||
|
if (this == memContextTop())
|
||||||
|
ERROR_THROW(AssertError, "top context may not have a callback");
|
||||||
|
|
||||||
|
// Error if callback has already been set - there may be valid use cases for this but error until one is found
|
||||||
|
if (this->callbackFunction)
|
||||||
|
ERROR_THROW(AssertError, "callback is already set for context '%s'", this->name);
|
||||||
|
|
||||||
|
// Set callback function and argument
|
||||||
|
this->callbackFunction = callbackFunction;
|
||||||
|
this->callbackArgument = callbackArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Allocate memory in the memory context and optionally zero it.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
static void *
|
||||||
|
memContextAlloc(size_t size, bool zero)
|
||||||
|
{
|
||||||
|
// Find space for the new allocation
|
||||||
|
int allocIdx;
|
||||||
|
|
||||||
|
for (allocIdx = 0; allocIdx < memContextCurrent()->allocListSize; allocIdx++)
|
||||||
|
if (!memContextCurrent()->allocList[allocIdx])
|
||||||
|
break;
|
||||||
|
|
||||||
|
// If no space was found then allocate more
|
||||||
|
if (allocIdx == memContextCurrent()->allocListSize)
|
||||||
|
{
|
||||||
|
// Only the top context will not have initial space for allocations
|
||||||
|
if (memContextCurrent()->allocListSize == 0)
|
||||||
|
{
|
||||||
|
// Allocate memory before modifying anything else in case there is an error
|
||||||
|
memContextCurrent()->allocList = memAllocInternal(sizeof(void *) * MEM_CONTEXT_ALLOC_INITIAL_SIZE, true);
|
||||||
|
|
||||||
|
// Set new size
|
||||||
|
memContextCurrent()->allocListSize = MEM_CONTEXT_ALLOC_INITIAL_SIZE;
|
||||||
|
}
|
||||||
|
// Else grow the list
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Calculate new list size
|
||||||
|
int allocListSizeNew = memContextCurrent()->allocListSize * 2;
|
||||||
|
|
||||||
|
// ReAllocate memory before modifying anything else in case there is an error
|
||||||
|
memContextCurrent()->allocList = memReAllocInternal(
|
||||||
|
memContextCurrent()->allocList, sizeof(void *) * memContextCurrent()->contextChildListSize,
|
||||||
|
sizeof(MemContext *) * allocListSizeNew, true);
|
||||||
|
|
||||||
|
// Set new size
|
||||||
|
memContextCurrent()->allocListSize = allocListSizeNew;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the memory
|
||||||
|
memContextCurrent()->allocList[allocIdx] = memAllocInternal(size, zero);
|
||||||
|
|
||||||
|
// Return buffer
|
||||||
|
return memContextCurrent()->allocList[allocIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Allocate zeroed memory in the memory context
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
void *
|
||||||
|
memNew(size_t size)
|
||||||
|
{
|
||||||
|
return memContextAlloc(size, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Allocate memory in the memory context without initializing it
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
void *
|
||||||
|
memNewRaw(size_t size)
|
||||||
|
{
|
||||||
|
return memContextAlloc(size, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Free a memory allocation in the context
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
void
|
||||||
|
memFree(void *buffer)
|
||||||
|
{
|
||||||
|
// Error if buffer is null
|
||||||
|
if (!buffer)
|
||||||
|
ERROR_THROW(AssertError, "unable to free null allocation");
|
||||||
|
|
||||||
|
// Find memory to free
|
||||||
|
int allocIdx;
|
||||||
|
|
||||||
|
for (allocIdx = 0; allocIdx < memContextCurrent()->allocListSize; allocIdx++)
|
||||||
|
if (memContextCurrent()->allocList[allocIdx] == buffer)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Error if the buffer was not found
|
||||||
|
if (allocIdx == memContextCurrent()->allocListSize)
|
||||||
|
ERROR_THROW(AssertError, "unable to find allocation");
|
||||||
|
|
||||||
|
// Free the buffer
|
||||||
|
memFreeInternal(memContextCurrent()->allocList[allocIdx]);
|
||||||
|
memContextCurrent()->allocList[allocIdx] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Switch to the specified context and return the old context
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext *
|
||||||
|
memContextSwitch(MemContext *this)
|
||||||
|
{
|
||||||
|
// Error if context is not active
|
||||||
|
if (this->state != memContextStateActive)
|
||||||
|
ERROR_THROW(AssertError, "cannot switch to inactive context");
|
||||||
|
|
||||||
|
MemContext *memContextOld = memContextCurrent();
|
||||||
|
contextCurrent = this;
|
||||||
|
|
||||||
|
return memContextOld;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Return the top context
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext *
|
||||||
|
memContextTop()
|
||||||
|
{
|
||||||
|
return &contextTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Return the current context
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext *
|
||||||
|
memContextCurrent()
|
||||||
|
{
|
||||||
|
return contextCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Return the context name
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
const char *
|
||||||
|
memContextName(MemContext *this)
|
||||||
|
{
|
||||||
|
// Error if context is not active
|
||||||
|
if (this->state != memContextStateActive)
|
||||||
|
ERROR_THROW(AssertError, "cannot get name for inactive context");
|
||||||
|
|
||||||
|
return this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
memContextFree - free all memory used by the context and all child contexts
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
void
|
||||||
|
memContextFree(MemContext *this)
|
||||||
|
{
|
||||||
|
// If context is already freeing then return if memContextFree() is called again - this can happen in callbacks
|
||||||
|
if (this->state == memContextStateFreeing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Top context cannot be freed
|
||||||
|
if (this == memContextTop())
|
||||||
|
ERROR_THROW(AssertError, "cannot free top context");
|
||||||
|
|
||||||
|
// Current context cannot be freed
|
||||||
|
if (this == memContextCurrent())
|
||||||
|
ERROR_THROW(AssertError, "cannot free current context '%s'", this->name);
|
||||||
|
|
||||||
|
// Error if context is not active
|
||||||
|
if (this->state != memContextStateActive)
|
||||||
|
ERROR_THROW(AssertError, "cannot free inactive context");
|
||||||
|
|
||||||
|
// Free child contexts
|
||||||
|
if (this->contextChildListSize > 0)
|
||||||
|
for (int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
|
||||||
|
if (this->contextChildList[contextIdx] && this->contextChildList[contextIdx]->state == memContextStateActive)
|
||||||
|
memContextFree(this->contextChildList[contextIdx]);
|
||||||
|
|
||||||
|
// Set state to freeing now that there are no child contexts. Child contexts might need to interact with their parent while
|
||||||
|
// freeing so the parent needs to remain active until they are all gone.
|
||||||
|
this->state = memContextStateFreeing;
|
||||||
|
|
||||||
|
// Execute callback if defined
|
||||||
|
if (this->callbackFunction)
|
||||||
|
this->callbackFunction(this->callbackArgument);
|
||||||
|
|
||||||
|
// Free child context allocations
|
||||||
|
if (this->contextChildListSize > 0)
|
||||||
|
{
|
||||||
|
for (int contextIdx = 0; contextIdx < this->contextChildListSize; contextIdx++)
|
||||||
|
if (this->contextChildList[contextIdx])
|
||||||
|
memFreeInternal(this->contextChildList[contextIdx]);
|
||||||
|
|
||||||
|
memFreeInternal(this->contextChildList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free memory allocations
|
||||||
|
if (this->allocListSize > 0)
|
||||||
|
{
|
||||||
|
for (int allocIdx = 0; allocIdx < this->allocListSize; allocIdx++)
|
||||||
|
if (this->allocList[allocIdx])
|
||||||
|
memFreeInternal(this->allocList[allocIdx]);
|
||||||
|
|
||||||
|
memFreeInternal(this->allocList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the memory context so it can be used again
|
||||||
|
memset(this, 0, sizeof(MemContext));
|
||||||
|
}
|
152
src/common/memContext.h
Normal file
152
src/common/memContext.h
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory Context Manager
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#ifndef MEM_CONTEXT_H
|
||||||
|
#define MEM_CONTEXT_H
|
||||||
|
|
||||||
|
#include "common/error.h"
|
||||||
|
#include "common/type.h"
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory context names cannot be larger than this size (excluding terminator) or an error will be thrown
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#define MEM_CONTEXT_NAME_SIZE 64
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Define initial number of memory contexts
|
||||||
|
|
||||||
|
No space is reserved for child contexts when a new context is created because most contexts will be leaves. When a child context is
|
||||||
|
requested then space will be reserved for this many child contexts initially. When more space is needed the size will be doubled.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#define MEM_CONTEXT_INITIAL_SIZE 4
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Define initial number of memory allocations
|
||||||
|
|
||||||
|
Space is reserved for this many allocations when a context is created. When more space is needed the size will be doubled.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#define MEM_CONTEXT_ALLOC_INITIAL_SIZE 4
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory context object
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct MemContext MemContext;
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory context callback function type, useful for casts in memContextCallback()
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef void (*MemContextCallback)(void *callbackArgument);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory context management functions
|
||||||
|
|
||||||
|
MemContext *context = memContextNew();
|
||||||
|
MemContext *contextOld = memContextSwitch(context);
|
||||||
|
|
||||||
|
ERROR_TRY()
|
||||||
|
{
|
||||||
|
<Do something with the memory context>
|
||||||
|
}
|
||||||
|
ERROR_CATCH_ANY()
|
||||||
|
{
|
||||||
|
<only needed if the error renders the memory context useless - for instance in a constructor>
|
||||||
|
|
||||||
|
memContextFree(context);
|
||||||
|
RETHROW();
|
||||||
|
}
|
||||||
|
FINALLY
|
||||||
|
{
|
||||||
|
memContextSwitch(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Use the MEM_CONTEXT*() macros when possible rather than implement error-handling for every memory context block.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext *memContextNew(const char *name);
|
||||||
|
void memContextCallback(MemContext *this, void (*callbackFunction)(void *), void *callbackArgument);
|
||||||
|
MemContext *memContextSwitch(MemContext *this);
|
||||||
|
void memContextFree(MemContext *this);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory context accessors
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext *memContextCurrent();
|
||||||
|
MemContext *memContextTop();
|
||||||
|
const char *memContextName(MemContext *this);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Memory management
|
||||||
|
|
||||||
|
These functions always new/free within the current memory context.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
void *memNew(size_t size);
|
||||||
|
void *memNewRaw(size_t size);
|
||||||
|
void memFree(void *buffer);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Ensure that the old memory context is restored after the block executes (even on error)
|
||||||
|
|
||||||
|
MEM_CONTEXT_BEGIN(memContext)
|
||||||
|
{
|
||||||
|
<The mem context specified is now the current context>
|
||||||
|
<Old context can be accessed with the MEM_CONTEXT_OLD() macro>
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_END();
|
||||||
|
|
||||||
|
<Old memory context is restored>
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#define MEM_CONTEXT_BEGIN(memContext) \
|
||||||
|
{ \
|
||||||
|
/* Switch to the new memory context */ \
|
||||||
|
MemContext *MEM_CONTEXT_memContextOld = memContextSwitch(memContext); \
|
||||||
|
\
|
||||||
|
/* Try the statement block */ \
|
||||||
|
ERROR_TRY()
|
||||||
|
|
||||||
|
#define MEM_CONTEXT_OLD() \
|
||||||
|
MEM_CONTEXT_memContextOld
|
||||||
|
|
||||||
|
#define MEM_CONTEXT_END() \
|
||||||
|
/* Free the context on error */ \
|
||||||
|
ERROR_FINALLY() \
|
||||||
|
{ \
|
||||||
|
memContextSwitch(MEM_CONTEXT_OLD()); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Create a new context and make sure it is freed on error and old context is restored in all cases
|
||||||
|
|
||||||
|
MEM_CONTEXT_NEW_BEGIN(memContextName)
|
||||||
|
{
|
||||||
|
<The mem context created is now the current context and can be accessed with the MEM_CONTEXT_NEW() macro>
|
||||||
|
|
||||||
|
ObjectType *object = memNew(sizeof(ObjectType));
|
||||||
|
object->memContext = MEM_CONTEXT_NEW();
|
||||||
|
|
||||||
|
<Old context can be accessed with the MEM_CONTEXT_OLD() macro>
|
||||||
|
<On error the newly created context will be freed and the error rethrown>
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_NEW_END();
|
||||||
|
|
||||||
|
<Old memory context is restored>
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#define MEM_CONTEXT_NEW_BEGIN(memContextName) \
|
||||||
|
{ \
|
||||||
|
MemContext *MEM_CONTEXT_NEW_BEGIN_memContext = memContextNew(memContextName); \
|
||||||
|
\
|
||||||
|
MEM_CONTEXT_BEGIN(MEM_CONTEXT_NEW_BEGIN_memContext) \
|
||||||
|
|
||||||
|
#define MEM_CONTEXT_NEW() \
|
||||||
|
MEM_CONTEXT_NEW_BEGIN_memContext
|
||||||
|
|
||||||
|
#define MEM_CONTEXT_NEW_END() \
|
||||||
|
ERROR_CATCH_ANY() \
|
||||||
|
{ \
|
||||||
|
memContextSwitch(MEM_CONTEXT_OLD()); \
|
||||||
|
memContextFree(MEM_CONTEXT_NEW()); \
|
||||||
|
ERROR_RETHROW(); \
|
||||||
|
} \
|
||||||
|
MEM_CONTEXT_END(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -118,6 +118,16 @@ my $oTestDef =
|
|||||||
'common/errorType' => TESTDEF_COVERAGE_FULL,
|
'common/errorType' => TESTDEF_COVERAGE_FULL,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
&TESTDEF_NAME => 'mem-context',
|
||||||
|
&TESTDEF_TOTAL => 6,
|
||||||
|
&TESTDEF_C => true,
|
||||||
|
|
||||||
|
&TESTDEF_COVERAGE =>
|
||||||
|
{
|
||||||
|
'common/memContext' => TESTDEF_COVERAGE_FULL,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
&TESTDEF_NAME => 'http-client',
|
&TESTDEF_NAME => 'http-client',
|
||||||
&TESTDEF_TOTAL => 2,
|
&TESTDEF_TOTAL => 2,
|
||||||
|
285
test/src/module/common/memContextTest.c
Normal file
285
test/src/module/common/memContextTest.c
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Test Memory Contexts
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
testFree - test callback function
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
MemContext *memContextCallbackArgument = NULL;
|
||||||
|
|
||||||
|
void testFree(MemContext *this)
|
||||||
|
{
|
||||||
|
TEST_RESULT_INT(this->state, memContextStateFreeing, "state should be freeing before memContextFree() in callback");
|
||||||
|
memContextFree(this);
|
||||||
|
TEST_RESULT_INT(this->state, memContextStateFreeing, "state should still be freeing after memContextFree() in callback");
|
||||||
|
|
||||||
|
TEST_ERROR(
|
||||||
|
memContextCallback(this, (MemContextCallback)testFree, this),
|
||||||
|
AssertError, "cannot assign callback to inactive context");
|
||||||
|
|
||||||
|
TEST_ERROR(memContextSwitch(this), AssertError, "cannot switch to inactive context");
|
||||||
|
TEST_ERROR(memContextName(this), AssertError, "cannot get name for inactive context");
|
||||||
|
|
||||||
|
memContextCallbackArgument = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Test Run
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
void testRun()
|
||||||
|
{
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
if (testBegin("memAllocInternal(), memReAllocInternal(), and memFreeInternal()"))
|
||||||
|
{
|
||||||
|
TEST_ERROR(
|
||||||
|
memAllocInternal(-1, false), MemoryError,
|
||||||
|
sizeof(size_t) == 8 ? "unable to allocate 18446744073709551615 bytes" : "unable to allocate 4294967295 bytes");
|
||||||
|
|
||||||
|
TEST_ERROR(memFreeInternal(NULL), MemoryError, "unable to free null pointer");
|
||||||
|
|
||||||
|
// Check that bad realloc is caught
|
||||||
|
void *buffer = memAllocInternal(sizeof(size_t), false);
|
||||||
|
TEST_ERROR(
|
||||||
|
memReAllocInternal(buffer, sizeof(size_t), -1, false), MemoryError,
|
||||||
|
sizeof(size_t) == 8 ? "unable to reallocate 18446744073709551615 bytes" : "unable to reallocate 4294967295 bytes");
|
||||||
|
memFreeInternal(buffer);
|
||||||
|
|
||||||
|
// Normal memory allocation
|
||||||
|
buffer = memAllocInternal(sizeof(size_t), false);
|
||||||
|
buffer = memReAllocInternal(buffer, sizeof(size_t), sizeof(size_t) * 2, false);
|
||||||
|
memFreeInternal(buffer);
|
||||||
|
|
||||||
|
// Zeroed memory allocation
|
||||||
|
unsigned char *buffer2 = memAllocInternal(sizeof(size_t), true);
|
||||||
|
int expectedTotal = 0;
|
||||||
|
|
||||||
|
for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++)
|
||||||
|
if (buffer2[charIdx] == 0)
|
||||||
|
expectedTotal++;
|
||||||
|
|
||||||
|
TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all bytes are 0");
|
||||||
|
|
||||||
|
// Zeroed memory reallocation
|
||||||
|
memset(buffer2, 0xC7, sizeof(size_t));
|
||||||
|
|
||||||
|
buffer2 = memReAllocInternal(buffer2, sizeof(size_t), sizeof(size_t) * 2, true);
|
||||||
|
|
||||||
|
expectedTotal = 0;
|
||||||
|
|
||||||
|
for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++)
|
||||||
|
if (buffer2[charIdx] == 0xC7)
|
||||||
|
expectedTotal++;
|
||||||
|
|
||||||
|
TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all old bytes are filled");
|
||||||
|
|
||||||
|
expectedTotal = 0;
|
||||||
|
|
||||||
|
for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++)
|
||||||
|
if ((buffer2 + sizeof(size_t))[charIdx] == 0)
|
||||||
|
expectedTotal++;
|
||||||
|
|
||||||
|
TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all new bytes are 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
if (testBegin("memContextNew() and memContextFree()"))
|
||||||
|
{
|
||||||
|
// Make sure top context was created
|
||||||
|
TEST_RESULT_STR(memContextName(memContextTop()), "TOP", "top context should exist");
|
||||||
|
TEST_RESULT_INT(memContextTop()->contextChildListSize, 0, "top context should init with zero children");
|
||||||
|
TEST_RESULT_PTR(memContextTop()->contextChildList, NULL, "top context child list empty");
|
||||||
|
|
||||||
|
TEST_ERROR(memContextFree(memContextTop()), AssertError, "cannot free top context");
|
||||||
|
|
||||||
|
// Current context should equal top context
|
||||||
|
TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "top context == current context");
|
||||||
|
|
||||||
|
// Context name length errors
|
||||||
|
TEST_ERROR(memContextNew(""), AssertError, "context name length must be > 0 and <= 64");
|
||||||
|
TEST_ERROR(
|
||||||
|
memContextNew("12345678901234567890123456789012345678901234567890123456789012345"),
|
||||||
|
AssertError, "context name length must be > 0 and <= 64");
|
||||||
|
|
||||||
|
MemContext *memContext = memContextNew("test1");
|
||||||
|
TEST_RESULT_STR(memContextName(memContext), "test1", "test1 context name");
|
||||||
|
TEST_RESULT_PTR(memContext->contextParent, memContextTop(), "test1 context parent is top");
|
||||||
|
TEST_RESULT_INT(memContextTop()->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE, "initial top context child list size");
|
||||||
|
|
||||||
|
TEST_RESULT_PTR(memContextSwitch(memContext), memContextTop(), "switch returns top as old context");
|
||||||
|
TEST_RESULT_PTR(memContext, memContextCurrent(), "current context is now test1");
|
||||||
|
|
||||||
|
// Create enough mem contexts to use up the initially allocated block
|
||||||
|
for (int contextIdx = 1; contextIdx < MEM_CONTEXT_INITIAL_SIZE; contextIdx++)
|
||||||
|
{
|
||||||
|
memContextSwitch(memContextTop());
|
||||||
|
memContextNew("test-filler");
|
||||||
|
TEST_RESULT_BOOL(
|
||||||
|
memContextTop()->contextChildList[contextIdx]->state == memContextStateActive, true, "new context is active");
|
||||||
|
TEST_RESULT_STR(memContextName(memContextTop()->contextChildList[contextIdx]), "test-filler", "new contest name");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This forces the child context array to grow
|
||||||
|
memContextNew("test5");
|
||||||
|
TEST_RESULT_INT(memContextTop()->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE * 2, "increased child context list size");
|
||||||
|
|
||||||
|
// Free a context
|
||||||
|
memContextFree(memContextTop()->contextChildList[1]);
|
||||||
|
TEST_RESULT_BOOL(
|
||||||
|
memContextTop()->contextChildList[1]->state == memContextStateActive, false, "child context inactive after free");
|
||||||
|
TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "current context is top");
|
||||||
|
|
||||||
|
// Create a new context and it should end up in the same spot
|
||||||
|
memContextNew("test-reuse");
|
||||||
|
TEST_RESULT_BOOL(
|
||||||
|
memContextTop()->contextChildList[1]->state == memContextStateActive,
|
||||||
|
true, "new context in same index as freed context is active");
|
||||||
|
TEST_RESULT_STR(memContextName(memContextTop()->contextChildList[1]), "test-reuse", "new context name");
|
||||||
|
|
||||||
|
// Create a child context to test recursive free
|
||||||
|
memContextSwitch(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]);
|
||||||
|
memContextNew("test-reuse");
|
||||||
|
TEST_RESULT_PTR_NE(
|
||||||
|
memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]->contextChildList, NULL, "context child list is allocated");
|
||||||
|
TEST_RESULT_INT(
|
||||||
|
memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]->contextChildListSize, MEM_CONTEXT_INITIAL_SIZE,
|
||||||
|
"context child list initial size");
|
||||||
|
|
||||||
|
TEST_ERROR(
|
||||||
|
memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]),
|
||||||
|
AssertError, "cannot free current context 'test5'");
|
||||||
|
|
||||||
|
memContextSwitch(memContextTop());
|
||||||
|
memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]);
|
||||||
|
|
||||||
|
TEST_ERROR(
|
||||||
|
memContextFree(memContextTop()->contextChildList[MEM_CONTEXT_INITIAL_SIZE]),
|
||||||
|
AssertError, "cannot free inactive context");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
if (testBegin("memContextAlloc(), memNew*(), and memFree()"))
|
||||||
|
{
|
||||||
|
memContextSwitch(memContextTop());
|
||||||
|
memNew(sizeof(size_t));
|
||||||
|
|
||||||
|
MemContext *memContext = memContextNew("test-alloc");
|
||||||
|
memContextSwitch(memContext);
|
||||||
|
|
||||||
|
for (int allocIdx = 0; allocIdx <= MEM_CONTEXT_ALLOC_INITIAL_SIZE; allocIdx++)
|
||||||
|
{
|
||||||
|
unsigned char *buffer = memNew(sizeof(size_t));
|
||||||
|
|
||||||
|
// Check that the buffer is zeroed
|
||||||
|
int expectedTotal = 0;
|
||||||
|
|
||||||
|
for (int charIdx = 0; charIdx < sizeof(size_t); charIdx++)
|
||||||
|
if (buffer[charIdx] == 0)
|
||||||
|
expectedTotal++;
|
||||||
|
|
||||||
|
TEST_RESULT_INT(expectedTotal, sizeof(size_t), "all bytes are 0");
|
||||||
|
|
||||||
|
TEST_RESULT_INT(
|
||||||
|
memContextCurrent()->allocListSize,
|
||||||
|
allocIdx == MEM_CONTEXT_ALLOC_INITIAL_SIZE ? MEM_CONTEXT_ALLOC_INITIAL_SIZE * 2 : MEM_CONTEXT_ALLOC_INITIAL_SIZE,
|
||||||
|
"allocation list size");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsigned char *buffer= memNewRaw(sizeof(size_t));
|
||||||
|
|
||||||
|
TEST_ERROR(memFree(NULL), AssertError, "unable to free null allocation");
|
||||||
|
TEST_ERROR(memFree((void *)0x01), AssertError, "unable to find allocation");
|
||||||
|
memFree(buffer);
|
||||||
|
|
||||||
|
memContextSwitch(memContextTop());
|
||||||
|
memContextFree(memContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
if (testBegin("memContextCallback()"))
|
||||||
|
{
|
||||||
|
TEST_ERROR(memContextCallback(memContextTop(), NULL, NULL), AssertError, "top context may not have a callback");
|
||||||
|
|
||||||
|
MemContext *memContext = memContextNew("test-callback");
|
||||||
|
memContextCallback(memContext, (MemContextCallback)testFree, memContext);
|
||||||
|
TEST_ERROR(
|
||||||
|
memContextCallback(memContext, (MemContextCallback)testFree, memContext),
|
||||||
|
AssertError, "callback is already set for context 'test-callback'");
|
||||||
|
|
||||||
|
memContextFree(memContext);
|
||||||
|
TEST_RESULT_PTR(memContextCallbackArgument, memContext, "callback argument is context");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
if (testBegin("MEM_CONTEXT_BEGIN() and MEM_CONTEXT_END()"))
|
||||||
|
{
|
||||||
|
memContextSwitch(memContextTop());
|
||||||
|
MemContext *memContext = memContextNew("test-block");
|
||||||
|
|
||||||
|
// Check normal block
|
||||||
|
MEM_CONTEXT_BEGIN(memContext)
|
||||||
|
{
|
||||||
|
TEST_RESULT_STR(memContextName(memContextCurrent()), "test-block", "context is now test-block");
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_END();
|
||||||
|
|
||||||
|
TEST_RESULT_STR(memContextName(memContextCurrent()), "TOP", "context is now top");
|
||||||
|
|
||||||
|
// Check block that errors
|
||||||
|
TEST_ERROR(
|
||||||
|
MEM_CONTEXT_BEGIN(memContext)
|
||||||
|
{
|
||||||
|
TEST_RESULT_STR(memContextName(memContextCurrent()), "test-block", "context is now test-block");
|
||||||
|
ERROR_THROW(AssertError, "error in test block");
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_END(),
|
||||||
|
AssertError, "error in test block");
|
||||||
|
|
||||||
|
TEST_RESULT_STR(memContextName(memContextCurrent()), "TOP", "context is now top");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
if (testBegin("MEM_CONTEXT_NEW_BEGIN() and MEM_CONTEXT_NEW_END()"))
|
||||||
|
{
|
||||||
|
// ------------------------------------------------------------------------------------------------------------------------
|
||||||
|
// Successful context new block
|
||||||
|
char *memContextTestName = "test-new-block";
|
||||||
|
MemContext *memContext;
|
||||||
|
|
||||||
|
MEM_CONTEXT_NEW_BEGIN(memContextTestName)
|
||||||
|
{
|
||||||
|
memContext = MEM_CONTEXT_NEW();
|
||||||
|
TEST_RESULT_PTR(memContext, memContextCurrent(), "new mem context is current");
|
||||||
|
TEST_RESULT_STR(memContextName(memContext), memContextTestName, "context is now '%s'", memContextTestName);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_NEW_END();
|
||||||
|
|
||||||
|
TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "context is now 'TOP'");
|
||||||
|
TEST_RESULT_BOOL(memContext->state == memContextStateActive, true, "new mem context is still active");
|
||||||
|
memContextFree(memContext);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------------------------------
|
||||||
|
// Failed context new block
|
||||||
|
memContextTestName = "test-new-failed-block";
|
||||||
|
bool bCatch = false;
|
||||||
|
|
||||||
|
ERROR_TRY()
|
||||||
|
{
|
||||||
|
MEM_CONTEXT_NEW_BEGIN(memContextTestName)
|
||||||
|
{
|
||||||
|
memContext = MEM_CONTEXT_NEW();
|
||||||
|
TEST_RESULT_STR(memContextName(memContext), memContextTestName, "context is now '%s'", memContextTestName);
|
||||||
|
ERROR_THROW(AssertError, "create failed");
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_NEW_END();
|
||||||
|
}
|
||||||
|
ERROR_CATCH(AssertError)
|
||||||
|
{
|
||||||
|
bCatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_RESULT_BOOL(bCatch, true, "new context error was caught");
|
||||||
|
TEST_RESULT_PTR(memContextCurrent(), memContextTop(), "context is now 'TOP'");
|
||||||
|
TEST_RESULT_BOOL(memContext->state == memContextStateFree, true, "new mem context is not active");
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user