mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-03-03 14:52:21 +02:00
Add C memory contexts.
This commit is contained in:
parent
6f5186f9e6
commit
583a76f605
@ -40,6 +40,10 @@
|
||||
<release-item>
|
||||
<p>Page checksum module uses new C error handler.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<p>Add C memory contexts.</p>
|
||||
</release-item>
|
||||
</release-refactor-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,
|
||||
},
|
||||
},
|
||||
{
|
||||
&TESTDEF_NAME => 'mem-context',
|
||||
&TESTDEF_TOTAL => 6,
|
||||
&TESTDEF_C => true,
|
||||
|
||||
&TESTDEF_COVERAGE =>
|
||||
{
|
||||
'common/memContext' => TESTDEF_COVERAGE_FULL,
|
||||
},
|
||||
},
|
||||
{
|
||||
&TESTDEF_NAME => 'http-client',
|
||||
&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");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user