summaryrefslogblamecommitdiff
path: root/src/common/mempool.c
blob: 62db7b9e310036fd916d35992be99e7204a5e8e1 (plain) (tree)































                                                                           
                            
                                              
                                       






                                                                            


                          
                    


                                       




                                                                        




                                                  


  































                                                                                                        


                                                                                         
   




                                                           
                                     
                                      

                                    

                                            






























                                                               


                                 





















                                                                                                                    



                      

































                                                                                             


                       



















                                                                                 

                    
                                                                                                                                   

      








                                                                                                                                                          
            
                        
     
                     
      
























                                                             
                    

                                 

      


























                                                                



                                        














































                                                                                                        
                    
                                                                                                                                                                    

      








                                              
 
 
                


                        






                                        



                                                 
























































                                                                                                                                    
                    



                                                                                                                     
      

























                                                  



                         































                                                             
                    
                      

      
                              


                          




                                            
                    














                                                                                                                                                                         

      






                                               



                          


















                                                                             

                           
//
// Memory Pool Implementation (Threadsafe)
//
//
// Author: Florian Wilkemeyer <fw@f-ws.de>
//
// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
//
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef WIN32
#include "../common/winapi.h"
#else
#include <unistd.h>
#endif

#include "../common/cbasetypes.h"
#include "../common/showmsg.h"
#include "../common/mempool.h"
#include "../common/atomic.h"
#include "../common/spinlock.h"
#include "../common/thread.h"
#include "../common/malloc.h"
#include "../common/mutex.h"

#define ALIGN16 ra_align(16)
#define ALIGN_TO(x, a) (x + ( a - ( x % a) ) )
#define ALIGN_TO_16(x)  ALIGN_TO(x, 16)

#undef MEMPOOL_DEBUG
#define MEMPOOLASSERT


#define NODE_TO_DATA(x) ( ((char*)x) + sizeof(struct node) )
#define DATA_TO_NODE(x) ( (struct node*)(((char*)x) - sizeof(struct node)) )
struct ra_align(16) node {
    void    *next;
    void    *segment;
#ifdef MEMPOOLASSERT
    bool    used;
    uint64  magic;
#define NODE_MAGIC 0xBEEF00EAEACAFE07ll
#endif
};


// The Pointer to this struct is the base address of the segment itself.
struct pool_segment {
    mempool pool; // pool, this segment belongs to
    struct  pool_segment *next;
    int64   num_nodes_total;
    int64   num_bytes;
};


struct mempool {
    // Settings
    char *name;
    uint64  elem_size;
    uint64  elem_realloc_step;
    int64   elem_realloc_thresh;

    // Callbacks that get called for every node that gets allocated
    // Example usage: initialization of mutex/lock for each node.
    memPoolOnNodeAllocationProc     onalloc;
    memPoolOnNodeDeallocationProc   ondealloc;

    // Locks
    SPIN_LOCK segmentLock;
    SPIN_LOCK nodeLock;


    // Internal
    struct pool_segment *segments;
    struct node *free_list;

    volatile int64  num_nodes_total;
    volatile int64  num_nodes_free;

    volatile int64  num_segments;
    volatile int64  num_bytes_total;

    volatile int64  peak_nodes_used;        // Peak Node Usage
    volatile int64  num_realloc_events;     // Number of reallocations done. (allocate additional nodes)

    // list (used for global management such as allocator..)
    struct mempool *next;
} ra_align(8); // Dont touch the alignment, otherwise interlocked functions are broken ..


///
// Implementation:
//
static void segment_allocate_add(mempool p,  uint64 count);

static SPIN_LOCK l_mempoolListLock;
static mempool  l_mempoolList = NULL;
static rAthread l_async_thread = NULL;
static ramutex  l_async_lock = NULL;
static racond   l_async_cond = NULL;
static volatile int32 l_async_terminate = 0;

static void *mempool_async_allocator(void *x)
{
    mempool p;


    while (1) {
        if (l_async_terminate > 0)
            break;

        EnterSpinLock(&l_mempoolListLock);

        for (p = l_mempoolList;  p != NULL;  p = p->next) {

            if (p->num_nodes_free < p->elem_realloc_thresh) {
                // add new segment.
                segment_allocate_add(p, p->elem_realloc_step);
                // increase stats counter
                InterlockedIncrement64(&p->num_realloc_events);
            }

        }

        LeaveSpinLock(&l_mempoolListLock);

        ramutex_lock(l_async_lock);
        racond_wait(l_async_cond,   l_async_lock,  -1);
        ramutex_unlock(l_async_lock);
    }


    return NULL;
}//end: mempool_async_allocator()


void mempool_init()
{

    if (sizeof(struct node)%16 != 0) {
        ShowFatalError("mempool_init: struct node alignment failure.  %u != multiple of 16\n", sizeof(struct node));
        exit(EXIT_FAILURE);
    }

    // Global List start
    InitializeSpinLock(&l_mempoolListLock);
    l_mempoolList = NULL;

    // Initialize mutex + stuff needed for async allocator worker.
    l_async_terminate = 0;
    l_async_lock = ramutex_create();
    l_async_cond = racond_create();

    l_async_thread = rathread_createEx(mempool_async_allocator, NULL, 1024*1024,  RAT_PRIO_NORMAL);
    if (l_async_thread == NULL) {
        ShowFatalError("mempool_init: cannot spawn Async Allocator Thread.\n");
        exit(EXIT_FAILURE);
    }

}//end: mempool_init()


void mempool_final()
{
    mempool p, pn;

    ShowStatus("Mempool: Terminating async. allocation worker and remaining pools.\n");

    // Terminate worker / wait until its terminated.
    InterlockedIncrement(&l_async_terminate);
    racond_signal(l_async_cond);
    rathread_wait(l_async_thread, NULL);

    // Destroy cond var and mutex.
    racond_destroy(l_async_cond);
    ramutex_destroy(l_async_lock);

    // Free remaining mempools
    // ((bugged code! this should halppen, every mempool should
    //      be freed by the subsystem that has allocated it.)
    //
    EnterSpinLock(&l_mempoolListLock);
    p = l_mempoolList;
    while (1) {
        if (p == NULL)
            break;

        pn = p->next;

        ShowWarning("Mempool [%s] was not properly destroyed - forcing destroy.\n", p->name);
        mempool_destroy(p);

        p = pn;
    }
    LeaveSpinLock(&l_mempoolListLock);

}//end: mempool_final()


static void segment_allocate_add(mempool p,  uint64 count)
{

    // Required Memory:
    //  sz( segment )
    //  count * sz( real_node_size )
    //
    //  where real node size is:
    //      ALIGN_TO_16( sz( node ) ) + p->elem_size
    //  so the nodes usable address is  nodebase + ALIGN_TO_16(sz(node))
    //
    size_t total_sz;
    struct pool_segment *seg = NULL;
    struct node *nodeList = NULL;
    struct node *node = NULL;
    char *ptr = NULL;
    uint64 i;

    total_sz = ALIGN_TO_16(sizeof(struct pool_segment))
               + ((size_t)count * (sizeof(struct node) + (size_t)p->elem_size)) ;

#ifdef MEMPOOL_DEBUG
    ShowDebug("Mempool [%s] Segment AllocateAdd (num: %u, total size: %0.2fMiB)\n", p->name, count, (float)total_sz/1024.f/1024.f);
#endif

    // allocate! (spin forever until weve got the memory.)
    i=0;
    while (1) {
        ptr = (char *)aMalloc(total_sz);
        if (ptr != NULL) break;

        i++; // increase failcount.
        if (!(i & 7)) {
            ShowWarning("Mempool [%s] Segment AllocateAdd => System seems to be Out of Memory (%0.2f MiB). Try #%u\n", (float)total_sz/1024.f/1024.f,  i);
#ifdef WIN32
            Sleep(1000);
#else
            sleep(1);
#endif
        } else {
            rathread_yield(); /// allow/force vuln. ctxswitch
        }
    }//endwhile: allocation spinloop.

    // Clear Memory.
    memset(ptr, 0x00, total_sz);

    // Initialize segment struct.
    seg = (struct pool_segment *)ptr;
    ptr += ALIGN_TO_16(sizeof(struct pool_segment));

    seg->pool = p;
    seg->num_nodes_total = count;
    seg->num_bytes = total_sz;


    // Initialze nodes!
    nodeList = NULL;
    for (i = 0; i < count; i++) {
        node = (struct node *)ptr;
        ptr += sizeof(struct node);
        ptr += p->elem_size;

        node->segment = seg;
#ifdef MEMPOOLASSERT
        node->used = false;
        node->magic = NODE_MAGIC;
#endif

        if (p->onalloc != NULL)  p->onalloc(NODE_TO_DATA(node));

        node->next = nodeList;
        nodeList = node;
    }



    // Link in Segment.
    EnterSpinLock(&p->segmentLock);
    seg->next = p->segments;
    p->segments = seg;
    LeaveSpinLock(&p->segmentLock);

    // Link in Nodes
    EnterSpinLock(&p->nodeLock);
    nodeList->next = p->free_list;
    p->free_list = nodeList;
    LeaveSpinLock(&p->nodeLock);


    // Increase Stats:
    InterlockedExchangeAdd64(&p->num_nodes_total,  count);
    InterlockedExchangeAdd64(&p->num_nodes_free,   count);
    InterlockedIncrement64(&p->num_segments);
    InterlockedExchangeAdd64(&p->num_bytes_total,   total_sz);

}//end: segment_allocate_add()


mempool mempool_create(const char *name,
                       uint64 elem_size,
                       uint64 initial_count,
                       uint64 realloc_count,
                       memPoolOnNodeAllocationProc onNodeAlloc,
                       memPoolOnNodeDeallocationProc onNodeDealloc)
{
    //..
    uint64 realloc_thresh;
    mempool pool;
    pool = (mempool)aCalloc(1,  sizeof(struct mempool));

    if (pool == NULL) {
        ShowFatalError("mempool_create: Failed to allocate %u bytes memory.\n", sizeof(struct mempool));
        exit(EXIT_FAILURE);
    }

    // Check minimum initial count / realloc count requirements.
    if (initial_count < 50)
        initial_count = 50;
    if (realloc_count < 50)
        realloc_count = 50;

    // Set Reallocation threshold to 5% of realloc_count, at least 10.
    realloc_thresh = (realloc_count/100)*5; //
    if (realloc_thresh < 10)
        realloc_thresh = 10;

    // Initialize members..
    pool->name = aStrdup(name);
    pool->elem_size = ALIGN_TO_16(elem_size);
    pool->elem_realloc_step = realloc_count;
    pool->elem_realloc_thresh = realloc_thresh;
    pool->onalloc = onNodeAlloc;
    pool->ondealloc = onNodeDealloc;

    InitializeSpinLock(&pool->segmentLock);
    InitializeSpinLock(&pool->nodeLock);

    // Initial Statistic values:
    pool->num_nodes_total = 0;
    pool->num_nodes_free = 0;
    pool->num_segments = 0;
    pool->num_bytes_total = 0;
    pool->peak_nodes_used = 0;
    pool->num_realloc_events = 0;

    //
#ifdef MEMPOOL_DEBUG
    ShowDebug("Mempool [%s] Init (ElemSize: %u,  Initial Count: %u,  Realloc Count: %u)\n", pool->name,  pool->elem_size,  initial_count,  pool->elem_realloc_step);
#endif

    // Allocate first segment directly :)
    segment_allocate_add(pool, initial_count);


    // Add Pool to the global pool list
    EnterSpinLock(&l_mempoolListLock);
    pool->next = l_mempoolList;
    l_mempoolList = pool;
    LeaveSpinLock(&l_mempoolListLock);


    return pool;
}//end: mempool_create()


void mempool_destroy(mempool p)
{
    struct  pool_segment *seg, *segnext;
    struct  node *niter;
    mempool piter, pprev;
    char *ptr;
    int64 i;

#ifdef MEMPOOL_DEBUG
    ShowDebug("Mempool [%s] Destroy\n", p->name);
#endif

    // Unlink from global list.
    EnterSpinLock(&l_mempoolListLock);
    piter = l_mempoolList;
    pprev = l_mempoolList;
    while (1) {
        if (piter == NULL)
            break;


        if (piter == p) {
            // unlink from list,
            //
            if (pprev == l_mempoolList) {
                // this (p) is list begin. so set next as head.
                l_mempoolList = p->next;
            } else {
                // replace prevs next wuth our next.
                pprev->next = p->next;
            }
            break;
        }

        pprev = piter;
        piter = piter->next;
    }

    p->next = NULL;
    LeaveSpinLock(&l_mempoolListLock);


    // Get both locks.
    EnterSpinLock(&p->segmentLock);
    EnterSpinLock(&p->nodeLock);


    if (p->num_nodes_free != p->num_nodes_total)
        ShowWarning("Mempool [%s] Destroy - %u nodes are not freed properly!\n", p->name, (p->num_nodes_total - p->num_nodes_free));

    // Free All Segments (this will also free all nodes)
    // The segment pointer is the base pointer to the whole segment.
    seg = p->segments;
    while (1) {
        if (seg == NULL)
            break;

        segnext = seg->next;

        // ..
        if (p->ondealloc != NULL) {
            // walk over the segment, and call dealloc callback!
            ptr = (char *)seg;
            ptr += ALIGN_TO_16(sizeof(struct pool_segment));
            for (i = 0; i < seg->num_nodes_total; i++) {
                niter = (struct node *)ptr;
                ptr += sizeof(struct node);
                ptr += p->elem_size;
#ifdef MEMPOOLASSERT
                if (niter->magic != NODE_MAGIC) {
                    ShowError("Mempool [%s] Destroy - walk over segment - node %p invalid magic!\n", p->name, niter);
                    continue;
                }
#endif

                p->ondealloc(NODE_TO_DATA(niter));


            }
        }//endif: ondealloc callback?

        // simple ..
        aFree(seg);

        seg = segnext;
    }

    // Clear node ptr
    p->free_list = NULL;
    InterlockedExchange64(&p->num_nodes_free, 0);
    InterlockedExchange64(&p->num_nodes_total, 0);
    InterlockedExchange64(&p->num_segments, 0);
    InterlockedExchange64(&p->num_bytes_total, 0);

    LeaveSpinLock(&p->nodeLock);
    LeaveSpinLock(&p->segmentLock);

    // Free pool itself :D
    aFree(p->name);
    aFree(p);

}//end: mempool_destroy()


void *mempool_node_get(mempool p)
{
    struct node *node;
    int64 num_used;

    if (p->num_nodes_free < p->elem_realloc_thresh)
        racond_signal(l_async_cond);

    while (1) {

        EnterSpinLock(&p->nodeLock);

        node = p->free_list;
        if (node != NULL)
            p->free_list = node->next;

        LeaveSpinLock(&p->nodeLock);

        if (node != NULL)
            break;

        rathread_yield();
    }

    InterlockedDecrement64(&p->num_nodes_free);

    // Update peak value
    num_used = (p->num_nodes_total - p->num_nodes_free);
    if (num_used > p->peak_nodes_used) {
        InterlockedExchange64(&p->peak_nodes_used, num_used);
    }

#ifdef MEMPOOLASSERT
    node->used = true;
#endif

    return NODE_TO_DATA(node);
}//end: mempool_node_get()


void mempool_node_put(mempool p, void *data)
{
    struct node *node;

    node = DATA_TO_NODE(data);
#ifdef MEMPOOLASSERT
    if (node->magic != NODE_MAGIC) {
        ShowError("Mempool [%s] node_put failed, given address (%p) has invalid magic.\n", p->name,  data);
        return; // lost,
    }

    {
        struct pool_segment *node_seg = node->segment;
        if (node_seg->pool != p) {
            ShowError("Mempool [%s] node_put faild, given node (data address %p) doesnt belongs to this pool. ( Node Origin is [%s] )\n", p->name, data, node_seg->pool);
            return;
        }
    }

    // reset used flag.
    node->used = false;
#endif

    //
    EnterSpinLock(&p->nodeLock);
    node->next = p->free_list;
    p->free_list = node;
    LeaveSpinLock(&p->nodeLock);

    InterlockedIncrement64(&p->num_nodes_free);

}//end: mempool_node_put()


mempool_stats mempool_get_stats(mempool pool)
{
    mempool_stats stats;

    // initialize all with zeros
    memset(&stats, 0x00, sizeof(mempool_stats));

    stats.num_nodes_total   = pool->num_nodes_total;
    stats.num_nodes_free    = pool->num_nodes_free;
    stats.num_nodes_used    = (stats.num_nodes_total - stats.num_nodes_free);
    stats.num_segments      = pool->num_segments;
    stats.num_realloc_events= pool->num_realloc_events;
    stats.peak_nodes_used   = pool->peak_nodes_used;
    stats.num_bytes_total   = pool->num_bytes_total;

    // Pushing such a large block over the stack as return value isnt nice
    // but lazy :) and should be okay in this case (Stats / Debug..)
    // if you dont like it - feel free and refactor it.
    return stats;
}//end: mempool_get_stats()