|
|
|
@ -0,0 +1,318 @@ |
|
|
|
#include "bufferpool.h" |
|
|
|
#include <glib.h> |
|
|
|
#include <stdbool.h> |
|
|
|
#include "obj.h" |
|
|
|
|
|
|
|
#define ALIGN 8 // bytes |
|
|
|
|
|
|
|
struct bufferpool { |
|
|
|
void *(*alloc)(size_t); |
|
|
|
void (*dealloc)(void *); |
|
|
|
void (*dealloc2)(void *, size_t); |
|
|
|
size_t shard_size; |
|
|
|
mutex_t lock; |
|
|
|
GQueue empty_shards; |
|
|
|
GQueue full_shards; |
|
|
|
bool destroy; |
|
|
|
}; |
|
|
|
|
|
|
|
struct bpool_shard { |
|
|
|
struct bufferpool *bp; |
|
|
|
unsigned int refs; |
|
|
|
void *buf; |
|
|
|
void *end; |
|
|
|
size_t size; |
|
|
|
void *head; |
|
|
|
bool full; |
|
|
|
unsigned int (*recycle)(void *); |
|
|
|
void *arg; |
|
|
|
}; |
|
|
|
|
|
|
|
// sorted list of all shards for quick bsearch |
|
|
|
static rwlock_t bpool_shards_lock; |
|
|
|
static GPtrArray *bpool_shards; |
|
|
|
|
|
|
|
static struct bufferpool *bufferpool_new_common(void *(*alloc)(size_t), size_t shard_size) { |
|
|
|
struct bufferpool *ret = g_new0(__typeof(*ret), 1); |
|
|
|
ret->alloc = alloc; |
|
|
|
ret->shard_size = shard_size; |
|
|
|
mutex_init(&ret->lock); |
|
|
|
g_queue_init(&ret->empty_shards); |
|
|
|
g_queue_init(&ret->full_shards); |
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
struct bufferpool *bufferpool_new(void *(*alloc)(size_t), void (*dealloc)(void *), size_t shard_size) { |
|
|
|
struct bufferpool *ret = bufferpool_new_common(alloc, shard_size); |
|
|
|
ret->dealloc = dealloc; |
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
struct bufferpool *bufferpool_new2(void *(*alloc)(size_t), void (*dealloc)(void *, size_t), size_t shard_size) { |
|
|
|
struct bufferpool *ret = bufferpool_new_common(alloc, shard_size); |
|
|
|
ret->dealloc2 = dealloc; |
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
// bufferpool is locked and shard is in "full" list but with zero refs |
|
|
|
static void bufferpool_recycle(struct bpool_shard *shard) { |
|
|
|
struct bufferpool *bp = shard->bp; |
|
|
|
shard->head = shard->buf; |
|
|
|
|
|
|
|
if (shard->recycle) |
|
|
|
shard->refs += shard->recycle(shard->arg); |
|
|
|
|
|
|
|
if (shard->refs == 0) { |
|
|
|
shard->full = false; |
|
|
|
GList *link = g_queue_find(&bp->full_shards, shard); // XXX avoid this |
|
|
|
g_queue_delete_link(&bp->full_shards, link); |
|
|
|
g_queue_push_tail(&bp->empty_shards, shard); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void bufferpool_dealloc(struct bpool_shard *shard) { |
|
|
|
struct bufferpool *bp = shard->bp; |
|
|
|
void *p = shard->buf; |
|
|
|
size_t len = shard->size; |
|
|
|
|
|
|
|
if (bp->dealloc) |
|
|
|
bp->dealloc(p); |
|
|
|
else |
|
|
|
bp->dealloc2(p, len); |
|
|
|
} |
|
|
|
|
|
|
|
// bufferpool is locked |
|
|
|
static void shard_check_full(struct bpool_shard *shard) { |
|
|
|
if (shard->refs != 0 || !shard->full) |
|
|
|
return; |
|
|
|
|
|
|
|
bufferpool_recycle(shard); |
|
|
|
} |
|
|
|
|
|
|
|
static int bpool_shards_sort(const void *A, const void *B) { |
|
|
|
const struct bpool_shard * const * const Ap = A, * const * const Bp = B; |
|
|
|
if ((*Ap)->buf < (*Bp)->buf) |
|
|
|
return -1; |
|
|
|
if ((*Ap)->buf > (*Bp)->buf) |
|
|
|
return 1; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static struct bpool_shard *bufferpool_new_shard(struct bufferpool *bp) { |
|
|
|
void *buf = bp->alloc(bp->shard_size); |
|
|
|
if (!buf) |
|
|
|
return NULL; |
|
|
|
|
|
|
|
struct bpool_shard *ret = g_new0(__typeof(*ret), 1); |
|
|
|
ret->bp = bp; |
|
|
|
ret->buf = buf; |
|
|
|
ret->size = bp->shard_size; |
|
|
|
ret->head = buf; |
|
|
|
ret->end = buf + bp->shard_size; |
|
|
|
|
|
|
|
RWLOCK_W(&bpool_shards_lock); |
|
|
|
|
|
|
|
g_ptr_array_add(bpool_shards, ret); |
|
|
|
g_ptr_array_sort(bpool_shards, bpool_shards_sort); |
|
|
|
|
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
void *bufferpool_alloc(struct bufferpool *bp, size_t len) { |
|
|
|
if (len > bp->shard_size) |
|
|
|
return NULL; |
|
|
|
|
|
|
|
LOCK(&bp->lock); |
|
|
|
|
|
|
|
// check existing shards if one has enough room. if not, create a new one |
|
|
|
|
|
|
|
struct bpool_shard *shard; |
|
|
|
|
|
|
|
while (true) { |
|
|
|
if (!bp->empty_shards.length) { |
|
|
|
shard = bufferpool_new_shard(bp); |
|
|
|
g_queue_push_tail(&bp->empty_shards, shard); |
|
|
|
break; |
|
|
|
} |
|
|
|
shard = bp->empty_shards.head->data; |
|
|
|
if (shard->head + len <= shard->end) |
|
|
|
break; |
|
|
|
|
|
|
|
g_queue_pop_head(&bp->empty_shards); |
|
|
|
g_queue_push_tail(&bp->full_shards, shard); |
|
|
|
|
|
|
|
shard->full = true; |
|
|
|
shard_check_full(shard); |
|
|
|
} |
|
|
|
|
|
|
|
// allocate buffer from shard |
|
|
|
|
|
|
|
void *ret = shard->head; |
|
|
|
shard->refs++; |
|
|
|
shard->head += ((len + ALIGN - 1) / ALIGN) * ALIGN; |
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
void *bufferpool_reserve(struct bufferpool *bp, unsigned int refs, unsigned int (*recycle)(void *), void *arg) { |
|
|
|
LOCK(&bp->lock); |
|
|
|
|
|
|
|
// get a completely empty shard. create one if needed |
|
|
|
|
|
|
|
struct bpool_shard *shard = g_queue_peek_head(&bp->empty_shards); |
|
|
|
if (shard && shard->head == shard->buf && shard->refs == 0) |
|
|
|
g_queue_pop_head(&bp->empty_shards); |
|
|
|
else |
|
|
|
shard = bufferpool_new_shard(bp); |
|
|
|
|
|
|
|
// set references, set recycle callback, move to full list |
|
|
|
shard->refs = refs; |
|
|
|
shard->full = true; |
|
|
|
g_queue_push_tail(&bp->full_shards, shard); |
|
|
|
shard->recycle = recycle; |
|
|
|
shard->arg = arg; |
|
|
|
|
|
|
|
return shard->buf; |
|
|
|
} |
|
|
|
|
|
|
|
static int bpool_shard_cmp(const void *buf, const void *ptr) { |
|
|
|
struct bpool_shard *const *sptr = ptr; |
|
|
|
struct bpool_shard *shard = *sptr; |
|
|
|
if (buf < shard->buf) |
|
|
|
return -1; |
|
|
|
if (buf >= shard->end) |
|
|
|
return 1; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
// bpool_shards_lock must be held |
|
|
|
static struct bpool_shard **bpool_find_shard_ptr(void *p) { |
|
|
|
return bsearch(p, bpool_shards->pdata, bpool_shards->len, |
|
|
|
sizeof(*bpool_shards->pdata), bpool_shard_cmp); |
|
|
|
} |
|
|
|
// bpool_shards_lock must be held |
|
|
|
static struct bpool_shard *bpool_find_shard(void *p) { |
|
|
|
struct bpool_shard **sp = bpool_find_shard_ptr(p); |
|
|
|
return sp ? *sp : NULL; |
|
|
|
} |
|
|
|
|
|
|
|
static void bpool_shard_destroy(struct bpool_shard *shard) { |
|
|
|
RWLOCK_W(&bpool_shards_lock); |
|
|
|
struct bpool_shard **ele = bpool_find_shard_ptr(shard->buf); |
|
|
|
size_t idx = (void **) ele - bpool_shards->pdata; |
|
|
|
g_ptr_array_remove_index(bpool_shards, idx); |
|
|
|
bufferpool_dealloc(shard); |
|
|
|
g_free(shard); |
|
|
|
} |
|
|
|
|
|
|
|
static void bpool_shard_delayed_destroy(struct bufferpool *bp, struct bpool_shard *shard) { |
|
|
|
if (shard->full) { |
|
|
|
GList *link = g_queue_find(&bp->full_shards, shard); |
|
|
|
g_queue_delete_link(&bp->full_shards, link); |
|
|
|
} |
|
|
|
else { |
|
|
|
GList *link = g_queue_find(&bp->empty_shards, shard); |
|
|
|
g_queue_delete_link(&bp->empty_shards, link); |
|
|
|
} |
|
|
|
bpool_shard_destroy(shard); |
|
|
|
} |
|
|
|
|
|
|
|
void bufferpool_unref(void *p) { |
|
|
|
if (!p) |
|
|
|
return; |
|
|
|
struct bpool_shard *shard; |
|
|
|
struct bufferpool *bpool; |
|
|
|
{ |
|
|
|
RWLOCK_R(&bpool_shards_lock); |
|
|
|
shard = bpool_find_shard(p); |
|
|
|
if (!shard) // should only happen during shutdown |
|
|
|
return; |
|
|
|
bpool = shard->bp; |
|
|
|
} |
|
|
|
{ |
|
|
|
LOCK(&bpool->lock); |
|
|
|
assert(shard->refs != 0); |
|
|
|
shard->refs--; |
|
|
|
// handle delayed destruction |
|
|
|
if (!bpool->destroy) { |
|
|
|
shard_check_full(shard); |
|
|
|
return; |
|
|
|
} |
|
|
|
// wait for refs to drop to zero, then remove/free shard, and destroy pool if no shards left |
|
|
|
if (shard->refs > 0) |
|
|
|
return; |
|
|
|
bpool_shard_delayed_destroy(bpool, shard); |
|
|
|
if (bpool->full_shards.length || bpool->empty_shards.length) |
|
|
|
return; // still some left |
|
|
|
} |
|
|
|
// no shards left, can destroy now |
|
|
|
bufferpool_destroy(bpool); |
|
|
|
} |
|
|
|
|
|
|
|
void bufferpool_release(void *p) { |
|
|
|
if (!p) |
|
|
|
return; |
|
|
|
struct bpool_shard *shard; |
|
|
|
struct bufferpool *bpool; |
|
|
|
{ |
|
|
|
RWLOCK_R(&bpool_shards_lock); |
|
|
|
shard = bpool_find_shard(p); |
|
|
|
bpool = shard->bp; |
|
|
|
} |
|
|
|
LOCK(&bpool->lock); |
|
|
|
assert(shard->refs != 0); |
|
|
|
shard->refs = 0; |
|
|
|
} |
|
|
|
|
|
|
|
void *bufferpool_ref(void *p) { |
|
|
|
if (!p) |
|
|
|
return NULL; |
|
|
|
struct bpool_shard *shard; |
|
|
|
struct bufferpool *bpool; |
|
|
|
{ |
|
|
|
RWLOCK_R(&bpool_shards_lock); |
|
|
|
shard = bpool_find_shard(p); |
|
|
|
bpool = shard->bp; |
|
|
|
} |
|
|
|
LOCK(&bpool->lock); |
|
|
|
assert(shard->refs != 0); |
|
|
|
shard->refs++; |
|
|
|
return p; |
|
|
|
} |
|
|
|
|
|
|
|
static void bpool_destroy_shards(GQueue *q) { |
|
|
|
GList *l = q->head; |
|
|
|
while (l) { |
|
|
|
GList *n = l->next; |
|
|
|
struct bpool_shard *shard = l->data; |
|
|
|
if (shard->refs == 0) { |
|
|
|
bpool_shard_destroy(shard); |
|
|
|
g_queue_delete_link(q, l); |
|
|
|
} |
|
|
|
l = n; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void bufferpool_destroy(struct bufferpool *bp) { |
|
|
|
{ |
|
|
|
LOCK(&bp->lock); |
|
|
|
bpool_destroy_shards(&bp->full_shards); |
|
|
|
bpool_destroy_shards(&bp->empty_shards); |
|
|
|
if (bp->full_shards.length || bp->empty_shards.length) { |
|
|
|
// deferred destruction |
|
|
|
bp->destroy = true; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
g_free(bp); |
|
|
|
} |
|
|
|
|
|
|
|
void bufferpool_init(void) { |
|
|
|
rwlock_init(&bpool_shards_lock); |
|
|
|
bpool_shards = g_ptr_array_new(); |
|
|
|
} |
|
|
|
|
|
|
|
void bufferpool_cleanup(void) { |
|
|
|
rwlock_destroy(&bpool_shards_lock); |
|
|
|
assert(bpool_shards->len == 0); |
|
|
|
g_ptr_array_free(bpool_shards, true); |
|
|
|
} |