blob: 8106c6184761c01b031f02fbb46804ce9fc374fe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include "cmockery.h"
#include "c.h"
#include "postgres.h"
#include "nodes/nodes.h"
#include "../aset.c"
#define NEW_ALLOC_SIZE 1024
extern MemoryAccount *MemoryAccountTreeLogicalRoot;
extern MemoryAccount *TopMemoryAccount;
extern MemoryAccount *MemoryAccountMemoryAccount;
/*
* This method will emulate the real ExceptionalCondition
* function by re-throwing the exception, essentially falling
* back to the next available PG_CATCH();
*/
void
_ExceptionalCondition()
{
PG_RE_THROW();
}
/*
* This method sets up MemoryContext tree as well as
* the basic MemoryAccount data structures.
*/
void SetupMemoryDataStructures(void **state)
{
MemoryContextInit();
}
/*
* This method cleans up MemoryContext tree and
* the MemoryAccount data structures.
*/
void TeardownMemoryDataStructures(void **state)
{
MemoryContextReset(TopMemoryContext); /* TopMemoryContext deletion is not supported */
/* These are needed to be NULL for calling MemoryContextInit() */
TopMemoryContext = NULL;
CurrentMemoryContext = NULL;
/*
* Memory accounts related variables need to be NULL before we
* try to setup memory account data structure again during the
* execution of the next test.
*/
MemoryAccountTreeLogicalRoot = NULL;
TopMemoryAccount = NULL;
MemoryAccountMemoryAccount = NULL;
RolloverMemoryAccount = NULL;
SharedChunkHeadersMemoryAccount = NULL;
ActiveMemoryAccount = NULL;
AlienExecutorMemoryAccount = NULL;
MemoryAccountMemoryContext = NULL;
}
/*
* Any change of global outstanding allocation balance should come
* from the combination of active memory account balance increase
* and any shared header allocation (SharedChunkHeadersMemoryAccount
* balance increase)
*/
void
test__MemoryAccounting_Allocate__ChargesOnlyActiveAccount(void **state)
{
MemoryAccount *newActiveAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
/* Make sure we have a new active account other than Rollover */
MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount);
assert_true(ActiveMemoryAccount == newActiveAccount);
uint64 prevOutstanding = MemoryAccountingOutstandingBalance;
uint64 prevSharedHeaderAlloc = SharedChunkHeadersMemoryAccount->allocated;
void *testAlloc = palloc(NEW_ALLOC_SIZE);
/*
* Any change of outstanding balance is coming from new allocation
* and the associated shared header allocation
*/
assert_true((newActiveAccount->allocated - newActiveAccount->freed) +
(SharedChunkHeadersMemoryAccount->allocated - prevSharedHeaderAlloc) ==
(MemoryAccountingOutstandingBalance - prevOutstanding));
/*
* We need to pfree as we have allocated this memory
* in the TopMemoryContext with a short-living memory account
*/
pfree(testAlloc);
}
/*
* Tests whether we adjust the outstanding balance during new
* allocation
*/
void
test__MemoryAccounting_Allocate__AdjustsOutstanding(void **state)
{
uint64 prevOutstanding = MemoryAccountingOutstandingBalance;
void *testAlloc = palloc(NEW_ALLOC_SIZE);
/* Outstanding balance should at least increase by NEW_ALLOC_SIZE */
assert_true((MemoryAccountingOutstandingBalance - prevOutstanding) >= NEW_ALLOC_SIZE);
/*
* We need to pfree as we have allocated this memory
* in the TopMemoryContext with a short-living memory account
*/
pfree(testAlloc);
}
/* Tests whether we adjust the peak balance during new allocation */
void
test__MemoryAccounting_Allocate__AdjustsPeak(void **state)
{
uint64 prevPeak = MemoryAccountingPeakBalance;
uint64 prevOutstanding = MemoryAccountingOutstandingBalance;
/* We need at least newAllocSize to get a new peak */
uint64 newAllocSize = prevPeak - prevOutstanding + 1;
void *testAlloc = palloc(newAllocSize);
/* We should have a new peak */
assert_true(MemoryAccountingPeakBalance > prevPeak);
/*
* We need to pfree as we have allocated this memory
* in the TopMemoryContext with a short-living memory account
*/
pfree(testAlloc);
}
/*
* Tests whether we free old generation memory from rollover account,
* irrespective of their owner account
*/
void
test__MemoryAccounting_Free__FreesOldGenFromRollover(void **state)
{
assert_true(ActiveMemoryAccount == TopMemoryAccount);
uint64 activeBalance = ActiveMemoryAccount->allocated - ActiveMemoryAccount->freed;
uint64 oldRolloverBalance = RolloverMemoryAccount->allocated - RolloverMemoryAccount->freed;
void *testAlloc = palloc(NEW_ALLOC_SIZE);
assert_true(activeBalance < (ActiveMemoryAccount->allocated - ActiveMemoryAccount->freed));
MemoryAccounting_Reset();
uint64 rolloverBalance = RolloverMemoryAccount->allocated - RolloverMemoryAccount->freed;
/* Rollover should assume the ownership of prev-generation allocations */
assert_true(rolloverBalance > 0 && rolloverBalance > oldRolloverBalance);
pfree(testAlloc);
/*
* After we freed our previous allocation, the rollover balance
* should be reduced by at least NEW_ALLOC_SIZE amount
*/
assert_true(RolloverMemoryAccount->allocated - RolloverMemoryAccount->freed <=
(rolloverBalance - NEW_ALLOC_SIZE));
}
/* Tests whether we adjust outstanding balance during freeing of memory */
void
test__MemoryAccounting_Free__AdjustsOutstanding(void **state)
{
uint64 oldOutstandingBalance = MemoryAccountingOutstandingBalance;
void *testAlloc = palloc(NEW_ALLOC_SIZE);
/* Outstanding balance should be increased by at least the allocation size */
assert_true(MemoryAccountingOutstandingBalance >= (oldOutstandingBalance + NEW_ALLOC_SIZE));
pfree(testAlloc);
/* Outstanding balance should be back to original */
assert_true(MemoryAccountingOutstandingBalance ==
oldOutstandingBalance);
}
/* Tests whether the peak balance is unchanged during free operation */
void
test__MemoryAccounting_Free__KeepsPeakUnchanged(void **state)
{
uint64 prevPeak = MemoryAccountingPeakBalance;
uint64 prevOutstanding = MemoryAccountingOutstandingBalance;
/*
* We want a new peak, which requires an allocation of at
* least newAllocSize
*/
uint64 newAllocSize = prevPeak - prevOutstanding + 1;
void *testAlloc = palloc(newAllocSize);
assert_true(MemoryAccountingPeakBalance > prevPeak);
uint64 newPeak = MemoryAccountingPeakBalance;
pfree(testAlloc);
/* Peak should not be altered by MemoryAccounting_Free() */
assert_true(MemoryAccountingPeakBalance == newPeak);
}
/* Tests whether the header sharing is working */
void
test__AllocAllocInfo__SharesHeader(void **state)
{
void *testAlloc1 = palloc(NEW_ALLOC_SIZE);
void *testAlloc2 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header1 = (StandardChunkHeader *)
((char *) testAlloc1 - STANDARDCHUNKHEADERSIZE);
StandardChunkHeader *header2 = (StandardChunkHeader *)
((char *) testAlloc2 - STANDARDCHUNKHEADERSIZE);
/* Both should share the same sharedHeader */
assert_true(header1->sharedHeader == header2->sharedHeader);
pfree(testAlloc1);
pfree(testAlloc2);
}
/*
* Tests whether we charge shared chunk header account during an allocation
* when header sharing is not possible
*/
void
test__AllocAllocInfo__ChargesSharedChunkHeadersMemoryAccount(void **state)
{
MemoryAccount *newActiveAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
/* Make sure we have a new active account to force a new shared header allocation */
MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount);
uint64 prevSharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed;
void *testAlloc = palloc(NEW_ALLOC_SIZE);
uint64 newSharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed;
/*
* A new sharedHeader should record the associated memory overhead
* in the SharedChunkHeadersMemoryAccount
*/
assert_true(prevSharedBalance < newSharedBalance);
pfree(testAlloc);
}
/* Tests whether we use null account header if there is no ActiveMemoryAccount */
void
test__AllocAllocInfo__UsesNullAccountHeader(void **state)
{
MemoryContext *newContext = AllocSetContextCreate(ErrorContext,
"TestContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
AllocSet newSet = (AllocSet)newContext;
assert_true(newSet->sharedHeaderList == NULL);
assert_true(newSet->nullAccountHeader == NULL);
MemoryAccount *oldActive = ActiveMemoryAccount;
MemoryAccount *oldShared = SharedChunkHeadersMemoryAccount;
/* Turning off memory monitoring */
ActiveMemoryAccount = NULL;
/* Also make SharedChunkHeadersMemoryAccount NULL to avoid assert failure */
SharedChunkHeadersMemoryAccount = NULL;
/* Allocate from the new context which should trigger a nullAccountHeader creation*/
void *testAlloc = MemoryContextAlloc(newContext, NEW_ALLOC_SIZE);
StandardChunkHeader *header = (StandardChunkHeader *)
((char *) testAlloc - STANDARDCHUNKHEADERSIZE);
/* The new chunk should use the nullAccountHeader of the Aset */
assert_true(header->sharedHeader != NULL && header->sharedHeader == newSet->nullAccountHeader);
ActiveMemoryAccount = oldActive;
SharedChunkHeadersMemoryAccount = oldShared;
pfree(testAlloc);
MemoryContextDelete(newContext);
}
/*
* Tests whether header sharing works even if the desired header
* is not at the head of sharedHeaderList
*/
void
test__AllocAllocInfo__LooksAheadInSharedHeaderList(void **state)
{
AllocSet set = (AllocSet)CurrentMemoryContext;
/*
* This should create or reuse one of the sharedHeader
* in the current context's sharedHeaderList
*/
void *testAlloc1 = palloc(NEW_ALLOC_SIZE);
MemoryAccount *newActiveAccount1 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
/* Make sure we have a new active account to force a new shared header allocation */
MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount1);
/* This will trigger a new sharedHeader creation because of the new ActiveMemoryAccount */
void *testAlloc2 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header1 = (StandardChunkHeader *)
((char *) testAlloc1 - STANDARDCHUNKHEADERSIZE);
StandardChunkHeader *header2 = (StandardChunkHeader *)
((char *) testAlloc2 - STANDARDCHUNKHEADERSIZE);
/* The old shared header should not be at the head of sharedHeaderList */
assert_true(set->sharedHeaderList != header1->sharedHeader);
assert_true(header1->sharedHeader != header2->sharedHeader);
/*
* This should restore old active account, so any further allocation
* should reuse header1, which is now *not* at the head of sharedHeaderList
*/
MemoryAccounting_SwitchAccount(oldActiveAccount);
void *testAlloc3 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header3 = (StandardChunkHeader *)
((char *) testAlloc3 - STANDARDCHUNKHEADERSIZE);
/* As we switched to previous account, we should be able to share the header */
assert_true(header3->sharedHeader == header1->sharedHeader);
/* Now we are triggering a third sharedHeader creation */
MemoryAccount *newActiveAccount2 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
/* Make sure we have a new active account to force a new shared header allocation */
oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount2);
void *testAlloc4 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header4 = (StandardChunkHeader *)
((char *) testAlloc4 - STANDARDCHUNKHEADERSIZE);
/* Make sure header4 got a new sharedHeader as we switched to a *new* memory account */
assert_true(header4->sharedHeader != header1->sharedHeader &&
header4->sharedHeader != header2->sharedHeader && header4->sharedHeader != header1->sharedHeader);
/* And the latest sharedHeader should be at the head of sharedHeaderList */
assert_true(header4->sharedHeader == set->sharedHeaderList);
/*
* Now restore the original account, making the very original
* sharedHeader eligible to be reused (a 3 level reuse lookahead)
*/
MemoryAccounting_SwitchAccount(oldActiveAccount);
void *testAlloc5 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header5 = (StandardChunkHeader *)
((char *) testAlloc5 - STANDARDCHUNKHEADERSIZE);
/*
* Make sure we were able to dig up the original sharedHeader
* by digging through the sharedHeaderList
*/
assert_true(header5->sharedHeader == header1->sharedHeader);
pfree(testAlloc1);
pfree(testAlloc2);
pfree(testAlloc3);
pfree(testAlloc4);
pfree(testAlloc5);
}
/* Tests whether we ignore sharedHeader if it is not from the current generation */
void
test__AllocAllocInfo__IgnoresOldGenSharedHeaders(void **state)
{
void *testAlloc1 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header1 = (StandardChunkHeader *)
((char *) testAlloc1 - STANDARDCHUNKHEADERSIZE);
MemoryAccounting_Reset();
/* Should not reuse sharedHeader as the generation has changed */
void *testAlloc2 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header2 = (StandardChunkHeader *)
((char *) testAlloc2 - STANDARDCHUNKHEADERSIZE);
/*
* No header sharing as the generation has changed during
* the call of MemoryAccounting_Reset()
*/
assert_true(header1->sharedHeader != header2->sharedHeader);
pfree(testAlloc1);
pfree(testAlloc2);
}
/*
* Tests whether we correctly allocate a new shared header and insert it
* into the sharedHeaderList
*/
void
test__AllocAllocInfo__InsertsIntoSharedHeaderList(void **state)
{
AllocSet set = (AllocSet)CurrentMemoryContext;
/*
* This should create or reuse one of the sharedHeader
* in the current context's sharedHeaderList
*/
void *testAlloc1 = palloc(NEW_ALLOC_SIZE);
MemoryAccount *newActiveAccount1 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
/* Make sure we have a new active account to force a new shared header allocation */
MemoryAccounting_SwitchAccount(newActiveAccount1);
/* This will trigger a new sharedHeader creation because of the new ActiveMemoryAccount */
void *testAlloc2 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header1 = (StandardChunkHeader *)
((char *) testAlloc1 - STANDARDCHUNKHEADERSIZE);
StandardChunkHeader *header2 = (StandardChunkHeader *)
((char *) testAlloc2 - STANDARDCHUNKHEADERSIZE);
/* Now we are triggering a third sharedHeader creation */
MemoryAccount *newActiveAccount2 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
/* Make sure we have a new active account to force a new shared header allocation */
MemoryAccounting_SwitchAccount(newActiveAccount2);
void *testAlloc3 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header3 = (StandardChunkHeader *)
((char *) testAlloc3 - STANDARDCHUNKHEADERSIZE);
/* Verify that the sharedHeaderList linked list looks good */
assert_true(set->sharedHeaderList == header3->sharedHeader &&
set->sharedHeaderList->next == header2->sharedHeader &&
set->sharedHeaderList->next->next == header1->sharedHeader &&
set->sharedHeaderList->next->next->next == NULL);
/*
* Create one more account to force creation of another sharedHeader, which
* should replace the earliest sharedHeader in the sharedHeaderList. We only
* look ahead up to depth 3, but we maintain all the sharedHeaders in the
* sharedHeaderList
*/
MemoryAccount *newActiveAccount3 = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
MemoryAccounting_SwitchAccount(newActiveAccount3);
void *testAlloc4 = palloc(NEW_ALLOC_SIZE);
StandardChunkHeader *header4 = (StandardChunkHeader *)
((char *) testAlloc4 - STANDARDCHUNKHEADERSIZE);
/*
* Verify that the sharedHeaderList linked list can go beyond
* 3 levels of lookahead by testing linked list up to depth 4
*/
assert_true(set->sharedHeaderList == header4->sharedHeader &&
set->sharedHeaderList->next == header3->sharedHeader &&
set->sharedHeaderList->next->next == header2->sharedHeader &&
set->sharedHeaderList->next->next->next == header1->sharedHeader &&
set->sharedHeaderList->next->next->next->next == NULL);
pfree(testAlloc1);
pfree(testAlloc2);
pfree(testAlloc3);
pfree(testAlloc4);
}
/* Tests whether SharedChunkHeadersMemoryAccount ignores the overhead of null Account header */
void
test__AllocFreeInfo__SharedChunkHeadersMemoryAccountIgnoresNullHeader(void **state)
{
/* This has two parts:
* 1. Check the assertion that nullAccountHeader creation requires
* SharedChunkHeadersMemoryAccount to be NULL
*
* 2. Reset the context that hosts the nullAccountHeader, and make sure
* that SharedChunkHeadersMemoryAccount balance does not get changed
*/
MemoryContext *newContext = AllocSetContextCreate(ErrorContext,
"TestContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
AllocSet newSet = (AllocSet)newContext;
assert_true(newSet->sharedHeaderList == NULL);
assert_true(newSet->nullAccountHeader == NULL);
MemoryAccount *oldActive = ActiveMemoryAccount;
MemoryAccount *oldShared = SharedChunkHeadersMemoryAccount;
/* Turning off memory monitoring */
ActiveMemoryAccount = NULL;
#ifdef USE_ASSERT_CHECKING
expect_any(ExceptionalCondition,conditionName);
expect_any(ExceptionalCondition,errorType);
expect_any(ExceptionalCondition,fileName);
expect_any(ExceptionalCondition,lineNumber);
will_be_called_with_sideeffect(ExceptionalCondition, &_ExceptionalCondition, NULL);
/* Test if within memory-limit strings cause assertion failure */
PG_TRY();
{
/*
* ActiveMemoryAccount is NULL, but SharedChunkHeadersMemoryAccount is
* *not* null. This should trigger an assertion
*/
void *testAlloc = MemoryContextAlloc(newContext, NEW_ALLOC_SIZE);
assert_true(false);
}
PG_CATCH();
{
}
PG_END_TRY();
#endif
/*
* Now make SharedChunkHeadersMemoryAccount NULL to avoid assert failure
* and allow creation of a nullAccountHeader
*/
SharedChunkHeadersMemoryAccount = NULL;
/* Allocate from the new context which should trigger a nullAccountHeader creation*/
void *testAlloc = MemoryContextAlloc(newContext, NEW_ALLOC_SIZE);
StandardChunkHeader *header = (StandardChunkHeader *)
((char *) testAlloc - STANDARDCHUNKHEADERSIZE);
/*
* Assert if we are using nullAccountHeader, as we have simulated absence
* of memory monitoring
*/
assert_true(header->sharedHeader != NULL && header->sharedHeader == newSet->nullAccountHeader);
/*
* Restore the SharedChunkHeadersMemoryAccount so that we can turn on
* sharedHeader balance releasing if a sharedHeader is deleted (except
* nullAccountHeader of course)
*/
SharedChunkHeadersMemoryAccount = oldShared;
/* Activate the memory accounting */
ActiveMemoryAccount = oldActive;
uint64 sharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed;
pfree(testAlloc);
/*
* As testAlloc is holding a pointer to nullAccountHeader, releasing that allocation
* should not change SharedChunkHeadersMemoryAccount balance
*/
assert_true(sharedBalance == SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed);
MemoryContextDelete(newContext);
}
/* Tests whether null Account header never gets freed */
void
test__AllocFreeInfo__ReusesNullHeader(void **state)
{
MemoryContext *newContext = AllocSetContextCreate(ErrorContext,
"TestContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
AllocSet newSet = (AllocSet)newContext;
assert_true(newSet->sharedHeaderList == NULL);
assert_true(newSet->nullAccountHeader == NULL);
MemoryAccount *oldActive = ActiveMemoryAccount;
MemoryAccount *oldShared = SharedChunkHeadersMemoryAccount;
/* Turning off memory monitoring */
ActiveMemoryAccount = NULL;
/* Also make SharedChunkHeadersMemoryAccount NULL to avoid assert failure */
SharedChunkHeadersMemoryAccount = NULL;
/* Allocate from the new context which should trigger a nullAccountHeader creation*/
void *testAlloc1 = MemoryContextAlloc(newContext, NEW_ALLOC_SIZE);
StandardChunkHeader *header1 = (StandardChunkHeader *)
((char *) testAlloc1 - STANDARDCHUNKHEADERSIZE);
/* Allocate another, and check if the nullAccountHeader is reused */
void *testAlloc2 = MemoryContextAlloc(newContext, NEW_ALLOC_SIZE);
StandardChunkHeader *header2 = (StandardChunkHeader *)
((char *) testAlloc2 - STANDARDCHUNKHEADERSIZE);
/*
* Make sure that the nullAccountHeader doesn't get reallocated
* every time and a singleton nullAccountHeader is shared between
* any chunks that are allocated before memory accounting is enabled
*/
assert_true(header1->sharedHeader != NULL && header1->sharedHeader == newSet->nullAccountHeader &&
header1->sharedHeader == header2->sharedHeader);
ActiveMemoryAccount = oldActive;
SharedChunkHeadersMemoryAccount = oldShared;
pfree(testAlloc1);
pfree(testAlloc2);
MemoryContextDelete(newContext);
}
/* Tests whether we are promptly freeing shared header that is no longer shared */
void
test__AllocFreeInfo__FreesObsoleteHeader(void **state)
{
MemoryAccount *newActiveAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
/* Make sure we have a new active account to force a new shared header allocation */
MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount);
uint64 prevSharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed;
void *testAlloc = palloc(NEW_ALLOC_SIZE);
uint64 newSharedBalance = SharedChunkHeadersMemoryAccount->allocated - SharedChunkHeadersMemoryAccount->freed;
assert_true(prevSharedBalance < newSharedBalance);
StandardChunkHeader *header = (StandardChunkHeader *)
((char *) testAlloc - STANDARDCHUNKHEADERSIZE);
AllocSet set = (AllocSet)CurrentMemoryContext;
SharedChunkHeader *headerPointer = header->sharedHeader;
assert_true(set->sharedHeaderList == headerPointer);
/*
* As no one else is using the sharedHeader of testAlloc,
* we should release upon pfree of testAlloc
*/
pfree(testAlloc);
/*
* The sharedHeaderList should no longer store the sharedHeader
* that we have just released. Note: the headerPointer is now
* and invalid pointer as we have already freed the shared header
* memory
*/
assert_true(set->sharedHeaderList != headerPointer);
}
/* Tests whether a free operation decreases balance of only the owning account */
void
test__AllocFreeInfo__FreesOnlyOwnerAccount(void **state)
{
MemoryAccount *newAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newAccount);
/* This chunk should record newAccount as the owner */
void *testAlloc = palloc(NEW_ALLOC_SIZE);
MemoryAccounting_SwitchAccount(oldActiveAccount);
assert_true(ActiveMemoryAccount != newAccount);
uint64 originalActiveBalance = ActiveMemoryAccount->allocated - ActiveMemoryAccount->freed;
uint64 newAccountBalance = newAccount->allocated - newAccount->freed;
uint64 newAccountFreed = newAccount->freed;
uint64 sharedFreed = SharedChunkHeadersMemoryAccount->freed;
uint64 prevOutstanding = MemoryAccountingOutstandingBalance;
pfree(testAlloc);
/*
* Make sure that the active account is unchanged while the owner account
* balance was reduced
*/
assert_true(ActiveMemoryAccount->allocated - ActiveMemoryAccount->freed == originalActiveBalance);
/* Balance was released from newAccount */
assert_true(newAccount->allocated - newAccount->freed <= newAccountBalance - NEW_ALLOC_SIZE);
/* The shared header should be released */
assert_true(SharedChunkHeadersMemoryAccount->freed - sharedFreed > 0);
/* All the difference in outstanding balance should come from newAccount
* (which was the owner of the chunk) balance change and the resulting
* shared header release
*/
assert_true(prevOutstanding - MemoryAccountingOutstandingBalance ==
newAccount->freed - newAccountFreed + SharedChunkHeadersMemoryAccount->freed - sharedFreed);
}
/* Tests whether a large allocation allocates dedicated block */
void
test__AllocSetAllocImpl__LargeAllocInNewBlock(void **state)
{
int chunkSize = ALLOC_CHUNK_LIMIT + 1;
/* This chunk should record newAccount as the owner */
void *testAlloc = palloc(chunkSize);
AllocChunk chunk = AllocPointerGetChunk(testAlloc);
AllocBlock block = (AllocBlock)(((char*)chunk) - ALLOC_BLOCKHDRSZ);
AllocSet set = (AllocSet)CurrentMemoryContext;
/*
* Make sure that the block pointer is the same as the *second* block in the blocks
* list (we insert the new block after the head, to keep the head available for
* more chunk allocation, if it is not entirely used yet). Also, ensure that block's
* freeptr is set to endptr (i.e., block is exclusively owned by this chunk).
*/
assert_true(set->blocks != NULL && set->blocks->next != NULL && set->blocks->next == block && set->blocks->next->endptr == set->blocks->next->freeptr);
pfree(testAlloc);
}
/* Tests whether a large allocation updates memory accounting outstanding balance */
void
test__AllocSetAllocImpl__LargeAllocInOutstandingBalance(void **state)
{
uint64 prevOutstanding = MemoryAccountingOutstandingBalance;
int chunkSize = ALLOC_CHUNK_LIMIT + 1;
/* This chunk should record newAccount as the owner */
void *testAlloc = palloc(chunkSize);
/*
* Outstanding balance should increase by at least the chunkSize
*/
assert_true(prevOutstanding + chunkSize <= MemoryAccountingOutstandingBalance);
pfree(testAlloc);
}
/* Tests whether a large allocation updates balance of the active memory account */
void
test__AllocSetAllocImpl__LargeAllocInActiveMemoryAccount(void **state)
{
MemoryAccount *newActiveAccount = MemoryAccounting_CreateAccount(0, MEMORY_OWNER_TYPE_Exec_Hash);
/* Make sure we have a new active account other than Rollover */
MemoryAccount *oldActiveAccount = MemoryAccounting_SwitchAccount(newActiveAccount);
uint64 prevOutstanding = MemoryAccountingOutstandingBalance;
uint64 prevSharedHeaderAlloc = SharedChunkHeadersMemoryAccount->allocated;
uint64 prevActiveAlloc = ActiveMemoryAccount->allocated;
int chunkSize = ALLOC_CHUNK_LIMIT + 1;
/* This chunk should record newAccount as the owner */
void *testAlloc = palloc(chunkSize);
/*
* All the new allocation should go to ActiveMemoryAccount, and the
* SharedChunkHeadersMemoryAccount should contribute to add up to
* the outstanding balance change
*/
assert_true((newActiveAccount->allocated >= prevActiveAlloc + chunkSize) &&
(SharedChunkHeadersMemoryAccount->allocated > prevSharedHeaderAlloc) &&
(newActiveAccount->allocated - newActiveAccount->freed) +
(SharedChunkHeadersMemoryAccount->allocated - prevSharedHeaderAlloc) ==
(MemoryAccountingOutstandingBalance - prevOutstanding));
/*
* We need to pfree as we have allocated this memory
* in the TopMemoryContext with a short-living memory account
*/
pfree(testAlloc);
}
/*
* Tests whether reallocating a small chunk into a large allocation
* updates memory accounting outstanding balance
*/
void
test__AllocSetRealloc__AdjustsOutstandingBalance(void **state)
{
/* This chunk should record newAccount as the owner */
void *testAlloc = palloc(NEW_ALLOC_SIZE);
AllocChunk chunk = AllocPointerGetChunk(testAlloc);
int prevSize = chunk->size;
uint64 prevOutstanding = MemoryAccountingOutstandingBalance;
/* Just double the allocation, which should still be under the ALLOC_CHUNK_LIMIT */
testAlloc = repalloc(testAlloc, NEW_ALLOC_SIZE * 2);
chunk = AllocPointerGetChunk(testAlloc);
int newSize = chunk->size;
uint64 balanceChange = newSize - prevSize;
/*
* Outstanding balance should increase by the "balanceChange"
*/
assert_true(balanceChange > 0 && prevOutstanding + balanceChange == MemoryAccountingOutstandingBalance);
prevOutstanding = MemoryAccountingOutstandingBalance;
prevSize = newSize;
int bigChunkSize = ALLOC_CHUNK_LIMIT + 1;
testAlloc = repalloc(testAlloc, bigChunkSize);
chunk = AllocPointerGetChunk(testAlloc);
newSize = chunk->size;
balanceChange = newSize - prevSize;
/*
* Outstanding balance should increase by the "balanceChange"
*/
assert_true(balanceChange > 0 && prevOutstanding + balanceChange == MemoryAccountingOutstandingBalance);
pfree(testAlloc);
}
int
main(int argc, char* argv[])
{
cmockery_parse_arguments(argc, argv);
const UnitTest tests[] = {
unit_test_setup_teardown(test__MemoryAccounting_Allocate__ChargesOnlyActiveAccount, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__MemoryAccounting_Allocate__AdjustsOutstanding, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__MemoryAccounting_Allocate__AdjustsPeak, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__MemoryAccounting_Free__FreesOldGenFromRollover, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__MemoryAccounting_Free__AdjustsOutstanding, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__MemoryAccounting_Free__KeepsPeakUnchanged, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocAllocInfo__SharesHeader, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocAllocInfo__ChargesSharedChunkHeadersMemoryAccount, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocAllocInfo__UsesNullAccountHeader, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocAllocInfo__LooksAheadInSharedHeaderList, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocAllocInfo__IgnoresOldGenSharedHeaders, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocAllocInfo__InsertsIntoSharedHeaderList, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocFreeInfo__SharedChunkHeadersMemoryAccountIgnoresNullHeader, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocFreeInfo__ReusesNullHeader, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocFreeInfo__FreesObsoleteHeader, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocFreeInfo__FreesOnlyOwnerAccount, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__MemoryAccounting_Allocate__ChargesOnlyActiveAccount, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocSetAllocImpl__LargeAllocInNewBlock, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocSetAllocImpl__LargeAllocInOutstandingBalance, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocSetAllocImpl__LargeAllocInActiveMemoryAccount, SetupMemoryDataStructures, TeardownMemoryDataStructures),
unit_test_setup_teardown(test__AllocSetRealloc__AdjustsOutstandingBalance, SetupMemoryDataStructures, TeardownMemoryDataStructures),
};
return run_tests(tests);
}