Rewrote ERS system in a more debug friendly way.

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@16743 54d463be-8e91-2dee-dedb-b68131a5f0ec
This commit is contained in:
greenboxal2 2012-09-04 19:54:00 +00:00
parent b3135cd23a
commit ea9f31706f

View File

@ -32,9 +32,10 @@
* * * *
* HISTORY: * * HISTORY: *
* 0.1 - Initial version * * 0.1 - Initial version *
* 1.0 - ERS Rework *
* * * *
* @version 0.1 - Initial version * * @version 1.0 - ERS Rework *
* @author Flavio @ Amazon Project * * @author GreenBox @ rAthena Project *
* @encoding US-ASCII * * @encoding US-ASCII *
* @see common#ers.h * * @see common#ers.h *
\*****************************************************************************/ \*****************************************************************************/
@ -46,473 +47,248 @@
#include "ers.h" #include "ers.h"
#ifndef DISABLE_ERS #ifndef DISABLE_ERS
/*****************************************************************************\
* (1) Private defines, structures and global variables. *
* ERS_BLOCK_ENTRIES - Number of entries in each block. *
* ERS_ROOT_SIZE - Maximum number of root entry managers. *
* ERLinkedList - Structure of a linked list of reusable entries. *
* ERS_impl - Class of an entry manager. *
* ers_root - Array of root entry managers. *
* ers_num - Number of root entry managers in the array. *
\*****************************************************************************/
/** #define ERS_ROOT_SIZE 256
* Number of entries in each block.
* @see #ers_obj_alloc_entry(ERS eri)
*/
#define ERS_BLOCK_ENTRIES 4096 #define ERS_BLOCK_ENTRIES 4096
/** typedef struct ers_instance_t;
* Maximum number of root entry managers. struct ers_list
* @private {
* @see #ers_root struct ers_list *Next;
* @see #ers_num };
*/
#define ERS_ROOT_SIZE 256
/** typedef struct
* Linked list of reusable entries. {
* The minimum size of the entries is the size of this structure. // Allocated object size, including ers_list size
* @private unsigned int ObjectSize;
* @see ERS_impl#reuse
*/
typedef struct ers_ll {
struct ers_ll *next;
} *ERLinkedList;
/** // Number of ers_instances referencing this
* Class of the object that manages entries of a certain size. int ReferenceCount;
* @param eri Public interface of the object
* @param reuse Linked list of reusable data entries
* @param blocks Array with blocks of entries
* @param free Number of unused entries in the last block
* @param num Number of blocks in the array
* @param max Current maximum capacity of the array
* @param destroy Destroy lock
* @param size Size of the entries of the manager
* @private
*/
typedef struct ers_impl {
/** // Our index in the ERS_Root array
* Public interface of the entry manager. unsigned int Index;
* @param alloc Allocate an entry from this manager
* @param free Free an entry allocated from this manager
* @param entry_size Return the size of the entries of this manager
* @param destroy Destroy this instance of the manager
* @public
*/
struct eri vtable;
/** // Reuse linked list
* Linked list of reusable entries. struct ers_list *ReuseList;
*/
ERLinkedList reuse;
/** // Memory blocks array
* Array with blocks of entries. unsigned char **Blocks;
*/
uint8 **blocks;
/** // Max number of blocks
* Number of unused entries in the last block. unsigned int Max;
*/
uint32 free;
/** // Free objects count
* Number of blocks in the array. unsigned int Free;
*/
uint32 num;
/** // Used objects count
* Current maximum capacity of the array. unsigned int Used;
*/ } ers_cache_t;
uint32 max;
/** typedef struct
* Destroy lock. {
*/ // Interface to ERS
uint32 destroy; struct eri VTable;
/** // Name, used for debbuging purpouses
* Size of the entries of the manager. char *Name;
*/
size_t size;
/** // Misc options
* Reference to this instance of the table enum ERSOptions Options;
*/
char *name;
/** // Our cache
* Options used by this instance ers_cache_t *Cache;
*/
enum ERSOptions options;
} *ERS_impl; // Count of objects in use, used for detecting memory leaks
unsigned int Count;
} ers_instance_t;
/**
* Root array with entry managers.
* @private
* @static
* @see #ERS_ROOT_SIZE
* @see #ers_num
*/
static ERS_impl ers_root[ERS_ROOT_SIZE];
/** // Array containing a pointer for all ers_cache structures
* Number of entry managers in the root array. static ers_cache_t *ERS_Root[ERS_ROOT_SIZE];
* @private
* @static
* @see #ERS_ROOT_SIZE
* @see #ers_root
*/
static uint32 ers_num = 0;
/*****************************************************************************\ static ers_cache_t *ers_find_cache(unsigned int size)
* (2) Object functions. * {
* ers_obj_alloc_entry - Allocate an entry from the manager. * int i;
* ers_obj_free_entry - Free an entry allocated from the manager. * ers_cache_t *cache;
* ers_obj_entry_size - Return the size of the entries of the manager. *
* ers_obj_destroy - Destroy the instance of the manager. * for (i = 0; i < ERS_ROOT_SIZE; i++)
\*****************************************************************************/ if (ERS_Root[i] != NULL && ERS_Root[i]->ObjectSize == size)
return ERS_Root[i];
CREATE(cache, ers_cache_t, 1);
cache->ObjectSize = size;
cache->ReferenceCount = 0;
cache->ReuseList = NULL;
cache->Blocks = NULL;
cache->Free = 0;
cache->Used = 0;
cache->Max = 0;
for (i = 0; i < ERS_ROOT_SIZE; i++)
{
if (ERS_Root[i] == NULL)
{
ERS_Root[i] = cache;
cache->Index = i;
break;
}
}
if (i >= ERS_ROOT_SIZE)
{
ShowFatalError("ers_new: too many root objects, increase ERS_ROOT_SIZE.\n"
"exiting the program...\n");
exit(EXIT_FAILURE);
}
return cache;
}
static void ers_free_cache(ers_cache_t *cache)
{
unsigned int i;
for (i = 0; i < cache->Used; i++)
aFree(cache->Blocks[i]);
ERS_Root[cache->Index] = NULL;
aFree(cache->Blocks);
aFree(cache);
}
/**
* Allocate an entry from this entry manager.
* If there are reusable entries available, it reuses one instead.
* @param self Interface of the entry manager
* @return An entry
* @see #ERS_BLOCK_ENTRIES
* @see #ERLinkedList
* @see ERS_impl::vtable#alloc
*/
static void *ers_obj_alloc_entry(ERS self) static void *ers_obj_alloc_entry(ERS self)
{ {
ERS_impl obj = (ERS_impl)self; ers_instance_t *instance = (ers_instance_t *)self;
void *ret; void *ret;
if (obj == NULL) { if (instance == NULL)
ShowError("ers::alloc : NULL object, aborting entry allocation.\n"); {
ShowError("ers_obj_alloc_entry: NULL object, aborting entry freeing.\n");
return NULL; return NULL;
} }
if (obj->reuse) { // Reusable entry if (instance->Cache->ReuseList != NULL)
ret = obj->reuse; {
obj->reuse = obj->reuse->next; ret = (void *)((unsigned int)instance->Cache->ReuseList + sizeof(struct ers_list));
} else if (obj->free) { // Unused entry instance->Cache->ReuseList = instance->Cache->ReuseList->Next;
obj->free--;
ret = &obj->blocks[obj->num -1][obj->free*obj->size];
} else { // allocate a new block
if (obj->num == obj->max) { // expand the block array
if (obj->max == UINT32_MAX) { // No more space for blocks
ShowFatalError("ers::alloc : maximum number of blocks reached, increase ERS_BLOCK_ENTRIES. (by %s)\n"
"exiting the program...\n",obj->name);
exit(EXIT_FAILURE);
} }
obj->max = (obj->max*4)+3; // left shift bits '11' - overflow won't happen else if (instance->Cache->Free > 0)
RECREATE(obj->blocks, uint8 *, obj->max); {
instance->Cache->Free--;
ret = &instance->Cache->Blocks[instance->Cache->Used - 1][instance->Cache->Free * instance->Cache->ObjectSize + sizeof(struct ers_list)];
} }
CREATE(obj->blocks[obj->num], uint8, obj->size*ERS_BLOCK_ENTRIES); else
obj->free = ERS_BLOCK_ENTRIES -1; {
ret = &obj->blocks[obj->num][obj->free*obj->size]; if (instance->Cache->Used == instance->Cache->Max)
obj->num++; {
instance->Cache->Max = (instance->Cache->Max * 4) + 3;
RECREATE(instance->Cache->Blocks, unsigned char *, instance->Cache->Max);
} }
CREATE(instance->Cache->Blocks[instance->Cache->Used], unsigned char, instance->Cache->ObjectSize * ERS_BLOCK_ENTRIES);
instance->Cache->Used++;
instance->Cache->Free = ERS_BLOCK_ENTRIES -1;
ret = &instance->Cache->Blocks[instance->Cache->Used - 1][instance->Cache->Free * instance->Cache->ObjectSize + sizeof(struct ers_list)];
}
instance->Count++;
return ret; return ret;
} }
/**
* Free an entry allocated from this manager.
* WARNING: Does not check if the entry was allocated by this manager.
* Freeing such an entry can lead to unexpected behaviour.
* @param self Interface of the entry manager
* @param entry Entry to be freed
* @see #ERLinkedList
* @see ERS_impl#reuse
* @see ERS_impl::vtable#free
*/
static void ers_obj_free_entry(ERS self, void *entry) static void ers_obj_free_entry(ERS self, void *entry)
{ {
ERS_impl obj = (ERS_impl)self; ers_instance_t *instance = (ers_instance_t *)self;
ERLinkedList reuse; struct ers_list *reuse = (struct ers_list *)((unsigned int)entry - sizeof(struct ers_list));
if (obj == NULL) { if (instance == NULL)
ShowError("ers::free : NULL object, aborting entry freeing.\n"); {
ShowError("ers_obj_free_entry: NULL object, aborting entry freeing.\n");
return; return;
} else if (entry == NULL) { }
ShowError("ers::free : NULL entry in obj '%s', nothing to free.\n",obj->name); else if (entry == NULL)
{
ShowError("ers_obj_free_entry: NULL entry, nothing to free.\n");
return; return;
} }
reuse = (ERLinkedList)entry; reuse->Next = instance->Cache->ReuseList;
reuse->next = obj->reuse; instance->Cache->ReuseList = reuse;
obj->reuse = reuse; instance->Count--;
} }
/**
* Return the size of the entries allocated from this manager.
* @param self Interface of the entry manager
* @return Size of the entries of this manager in bytes
* @see ERS_impl#size
* @see ERS_impl::vtable#entry_size
*/
static size_t ers_obj_entry_size(ERS self) static size_t ers_obj_entry_size(ERS self)
{ {
ERS_impl obj = (ERS_impl)self; ers_instance_t *instance = (ers_instance_t *)self;
if (obj == NULL) { if (instance == NULL)
ShowError("ers::entry_size : NULL object, returning 0.\n"); {
ShowError("ers_obj_entry_size: NULL object, aborting entry freeing.\n");
return 0; return 0;
} }
return obj->size; return instance->Cache->ObjectSize;
} }
/**
* Destroy this instance of the manager.
* The manager is actually only destroyed when all the instances are destroyed.
* When destroying the manager a warning is shown if the manager has
* missing/extra entries.
* @param self Interface of the entry manager
* @see #ERLinkedList
* @see ERS_impl::vtable#destroy
*/
static void ers_obj_destroy(ERS self) static void ers_obj_destroy(ERS self)
{ {
ERS_impl obj = (ERS_impl)self; ers_instance_t *instance = (ers_instance_t *)self;
ERLinkedList reuse,old;
uint32 i;
uint32 count;
if (obj == NULL) { if (instance == NULL)
ShowError("ers::destroy: NULL object, aborting instance destruction.\n"); {
ShowError("ers_obj_destroy: NULL object, aborting entry freeing.\n");
return; return;
} }
obj->destroy--; if (instance->Count > 0)
if (obj->destroy) if (!(instance->Options & ERS_OPT_CLEAR))
return; // Not last instance ShowWarning("Memory leak detected at ERS '%s', %d objects not freed.\n", instance->Name, instance->Count);
// Remove manager from root array if (--instance->Cache->ReferenceCount <= 0)
for (i = 0; i < ers_num; i++) { ers_free_cache(instance->Cache);
if (ers_root[i] == obj) {
ers_num--; aFree(instance);
if (i < ers_num) // put the last manager in the free slot
ers_root[i] = ers_root[ers_num];
break;
}
}
reuse = obj->reuse;
count = 0;
// Check for missing/extra entries
for (i = 0; i < obj->num; i++) {
if (i == 0) {
count = ERS_BLOCK_ENTRIES -obj->free;
} else if (count > UINT32_MAX -ERS_BLOCK_ENTRIES) {
count = UINT32_MAX;
break;
} else {
count += ERS_BLOCK_ENTRIES;
}
while (reuse && count) {
count--;
old = reuse;
reuse = reuse->next;
old->next = NULL; // this makes duplicate frees report as missing entries
}
}
if (count) { // missing entries
if( !(obj->options&ERS_OPT_CLEAR) )
ShowWarning("ers::destroy : %u entries missing in '%s' (possible double free), continuing destruction (entry size=%u).\n",
count, obj->name, obj->size);
} else if (reuse) { // extra entries
while (reuse && count != UINT32_MAX) {
count++;
reuse = reuse->next;
}
ShowWarning("ers::destroy : %u extra entries found in '%s', continuing destruction (entry size=%u).\n",
count, obj->name, obj->size);
}
// destroy the entry manager
if (obj->max) {
for (i = 0; i < obj->num; i++)
aFree(obj->blocks[i]); // release block of entries
aFree(obj->blocks); // release array of blocks
}
aFree(obj); // release manager
} }
/*****************************************************************************\ ERS ers_new(uint32 size, char *name, enum ERSOptions options)
* (3) Public functions. * {
* ers_new - Get a new instance of an entry manager. * ers_instance_t *instance;
* ers_report - Print a report about the current state. * CREATE(instance, ers_instance_t, 1);
* ers_force_destroy_all - Force the destruction of all the managers. *
\*****************************************************************************/
/** size += sizeof(struct ers_list);
* Get a new instance of the manager that handles the specified entry size. if (size % ERS_ALIGNED)
* Size has to greater than 0. size += ERS_ALIGNED - size % ERS_ALIGNED;
* If the specified size is smaller than a pointer, the size of a pointer is
* used instead.
* It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of
* ERS_ALIGNED that is greater or equal to size is what's actually used.
* @param The requested size of the entry in bytes
* @return Interface of the object
* @see #ERS_impl
* @see #ers_root
* @see #ers_num
*/
ERS ers_new(uint32 size, char *name, enum ERSOptions options) {
ERS_impl obj;
uint32 i;
if (size == 0) { instance->VTable.alloc = ers_obj_alloc_entry;
ShowError("ers_new: invalid size %u, aborting instance creation.\n", instance->VTable.free = ers_obj_free_entry;
size); instance->VTable.entry_size = ers_obj_entry_size;
return NULL; instance->VTable.destroy = ers_obj_destroy;
}
if (size < sizeof(struct ers_ll)) // Minimum size instance->Name = name;
size = sizeof(struct ers_ll); instance->Options = options;
if (size%ERS_ALIGNED) // Align size
size += ERS_ALIGNED -size%ERS_ALIGNED;
for (i = 0; i < ers_num; i++) { instance->Cache = ers_find_cache(size);
obj = ers_root[i]; instance->Cache->ReferenceCount++;
if (obj->size == size) {
// found a manager that handles the entry size instance->Count = 0;
obj->destroy++;
return &obj->vtable; return &instance->VTable;
}
}
// create a new manager to handle the entry size
if (ers_num == ERS_ROOT_SIZE) {
ShowFatalError("ers_alloc: too many root objects, increase ERS_ROOT_SIZE. (by %s)\n"
"exiting the program...\n",name);
exit(EXIT_FAILURE);
}
obj = (ERS_impl)aMalloc(sizeof(struct ers_impl));
// Public interface
obj->vtable.alloc = ers_obj_alloc_entry;
obj->vtable.free = ers_obj_free_entry;
obj->vtable.entry_size = ers_obj_entry_size;
obj->vtable.destroy = ers_obj_destroy;
// Block reusage system
obj->reuse = NULL;
obj->blocks = NULL;
obj->free = 0;
obj->num = 0;
obj->max = 0;
obj->destroy = 1;
// Properties
obj->size = size;
obj->options = options;
// Info
obj->name = name;
ers_root[ers_num++] = obj;
return &obj->vtable;
} }
/**
* Print a report about the current state of the Entry Reusage System.
* Shows information about the global system and each entry manager.
* The number of entries are checked and a warning is shown if extra reusable
* entries are found.
* The extra entries are included in the count of reusable entries.
* @see #ERLinkedList
* @see #ERS_impl
* @see #ers_root
* @see #ers_num
*/
void ers_report(void) void ers_report(void)
{ {
uint32 i; // FIXME: Someone use this? Is it really needed?
uint32 j;
uint32 used;
uint32 reusable;
uint32 extra;
ERLinkedList reuse;
ERS_impl obj;
// Root system report
ShowMessage(CL_BOLD"Entry Reusage System report:\n"CL_NORMAL);
ShowMessage("root array size : %u\n", ERS_ROOT_SIZE);
ShowMessage("root entry managers : %u\n", ers_num);
ShowMessage("entries per block : %u\n", ERS_BLOCK_ENTRIES);
for (i = 0; i < ers_num; i++) {
obj = ers_root[i];
reuse = obj->reuse;
used = 0;
reusable = 0;
// Count used and reusable entries
for (j = 0; j < obj->num; j++) {
if (j == 0) { // take into acount the free entries
used = ERS_BLOCK_ENTRIES -obj->free;
} else if (reuse) { // counting reusable entries
used = ERS_BLOCK_ENTRIES;
} else { // no more reusable entries, count remaining used entries
for (; j < obj->num; j++) {
if (used > UINT32_MAX -ERS_BLOCK_ENTRIES) { // overflow
used = UINT32_MAX;
break;
}
used += ERS_BLOCK_ENTRIES;
}
break;
}
while (used && reuse) { // count reusable entries
used--;
if (reusable != UINT32_MAX)
reusable++;
reuse = reuse->next;
}
}
// Count extra reusable entries
extra = 0;
while (reuse && extra != UINT32_MAX) {
extra++;
reuse = reuse->next;
}
// Entry manager report
ShowMessage(CL_BOLD"[Entry manager '%s' #%u report]\n"CL_NORMAL, obj->name, i);
ShowMessage("\tinstances : %u\n", obj->destroy);
ShowMessage("\tentry size : %u\n", obj->size);
ShowMessage("\tblock array size : %u\n", obj->max);
ShowMessage("\tallocated blocks : %u\n", obj->num);
ShowMessage("\tentries being used : %u\n", used);
ShowMessage("\tunused entries : %u\n", obj->free);
ShowMessage("\treusable entries : %u\n", reusable);
if (extra)
ShowMessage("\tWARNING - %u extra reusable entries were found.\n", extra);
}
ShowMessage("End of report\n");
} }
/**
* Forcibly destroy all the entry managers, checking for nothing.
* The system is left as if no instances or entries had ever been allocated.
* All previous entries and instances of the managers become invalid.
* The use of this is NOT recommended.
* It should only be used in extreme situations to make shure all the memory
* allocated by this system is released.
* @see #ERS_impl
* @see #ers_root
* @see #ers_num
*/
void ers_force_destroy_all(void) void ers_force_destroy_all(void)
{ {
uint32 i; int i;
uint32 j;
ERS_impl obj;
for (i = 0; i < ers_num; i++) { for (i = 0; i < ERS_ROOT_SIZE; i++)
obj = ers_root[i]; if (ERS_Root[i] != NULL)
if (obj->max) { ers_free_cache(ERS_Root[i]);
for (j = 0; j < obj->num; j++)
aFree(obj->blocks[j]); // block of entries
aFree(obj->blocks); // array of blocks
}
aFree(obj); // entry manager object
}
ers_num = 0;
} }
#endif /* not DISABLE_ERS */
#endif