diff options
| -rw-r--r-- | pk.h.in | 11 | ||||
| -rw-r--r-- | pkmem.h | 14 | ||||
| -rw-r--r-- | test/pkmem.cpp | 135 |
3 files changed, 153 insertions, 7 deletions
@@ -52,9 +52,14 @@ ******************************************************************************** * pkmem.h: def PK_IMPL_MEM before including pk.h to enable ad-hoc. * -* A bucketed memory manager. Allows the creation and management of up to a well -* known number of buckets. While this is thread-safe, the threading safety is -* a Grug implementation via a single mutex. PRs welcome. +* A bucketed memory manager. Allows for the creation and management of up to a +* well-defined number of buckets. +* +* Thread safety: Bucket creation and destruction is *not* thread-safe. On the +* other hand, the "pk_new" and "pk_delete" methods *are* thread-safe, but +* thread-safety is implemented per-bucket via a single mutex with long-running +* lock times. PRs for a more performant thread-safe strategy are welcome, +* complexity and benchmark depending. * * The following definitions (shown with defaults) can be overridden: * PK_DEFAULT_BUCKET_SIZE 256MB (used when bkt is NULL on first call) @@ -221,9 +221,9 @@ pk_memory_flush() void pk_memory_teardown_all() { - for (int64_t i = pk_bucket_head - 1; i > 0; --i) { - if (pk_buckets[i].ptr == nullptr) continue; - pk_bucket_destroy(&pk_buckets[i]); + for (int64_t i = pk_bucket_head; i > 0; --i) { + if (pk_buckets[i - 1].ptr == nullptr) continue; + pk_bucket_destroy(&pk_buckets[i - 1]); } pk_bucket_head = 0; } @@ -231,6 +231,7 @@ pk_memory_teardown_all() static int64_t pk_bucket_create_inner(int64_t sz, bool transient, const char* description) { + assert(pk_bucket_head < PK_MAX_BUCKET_COUNT && "pkmem.h: reserved bucket count exceeded"); #ifdef PK_MEMORY_DEBUGGER if (has_init_debug == false) { has_init_debug = true; @@ -246,6 +247,7 @@ pk_bucket_create_inner(int64_t sz, bool transient, const char* description) bkt->lastEmptyBlockIndex = 0; bkt->maxBlockCount = blockCount < 10 ? 10 : blockCount; bkt->blocks = (struct pk_memblock*)malloc(sz); + mtx_init(&bkt->mtx, mtx_plain); assert(bkt->blocks != nullptr && "failed to allocate memory"); #if 1 memset(bkt->blocks, 0, sz); @@ -292,6 +294,7 @@ pk_bucket_destroy(struct pk_membucket* bkt) bkt->blocks = CAFE_BABE(struct pk_memblock); bkt->ptr = CAFE_BABE(char); bkt->transient = false; + mtx_destroy(&bkt->mtx); #ifdef PK_MEMORY_DEBUGGER for (i = debug_alloc_head; i > -1; --i) { if (debug_all_allocs[i].bkt == bkt) { @@ -394,7 +397,10 @@ pk_new_bkt(size_t sz, size_t alignment, struct pk_membucket* bkt) break; } } - assert(block != nullptr && "memory corruption: failed to find bucket with enough space"); + if (block == nullptr) { + mtx_unlock(&bkt->mtx); + assert(block != nullptr && "memory corruption: not enough space in chosen bkt"); + } data = block->data + misalignment; #ifdef PK_MEMORY_DEBUGGER bool handled = bkt->transient; diff --git a/test/pkmem.cpp b/test/pkmem.cpp index cc7cf9c..8dc429a 100644 --- a/test/pkmem.cpp +++ b/test/pkmem.cpp @@ -1,10 +1,41 @@ #include "../pkmem.h" +#include <csetjmp> +#include <signal.h> +#include <thread> + +static bool expected_exit = false; +static bool caught = false; +static jmp_buf jmp_env; +void +exit(int code) +{ + if (expected_exit) { + caught = true; + longjmp(jmp_env, 1); + } else { + _exit(code); + } +} +// assert() calls abort(), handle +void +handle_assert_abort(int sig) +{ + if (expected_exit) { + caught = true; + longjmp(jmp_env, 1); + } else { + _exit(1); + } +} + int main(int argc, char *argv[]) { + signal(SIGABRT, handle_assert_abort); (void)argc; (void)argv; + int i; // pk_new<T> { @@ -12,6 +43,110 @@ int main(int argc, char *argv[]) fprintf(stdout, "some_dang_string: %p\n", some_dang_string); pk_delete<char>(some_dang_string, 64); } + pk_memory_teardown_all(); + + // assert bucket count + do + { + int r; + r = setjmp(jmp_env); + if (r == 1) { + if (expected_exit == true && caught == true) { + expected_exit = false; + caught = false; + PK_LOGV_INF("%s: successfully caught err.\n", __FILE__); + fflush(stdout); + fflush(stderr); + break; + } else { + goto uncaught_err; + } + } + for (i = 0; i < PK_MAX_BUCKET_COUNT + 1; ++i) { + caught = false; + expected_exit = true; + pk_membucket *bkt = pk_bucket_create("lol", 1024, false); + expected_exit = false; + (void)bkt; + } + goto uncaught_err; + } + while(false); + pk_memory_teardown_all(); + + // assert no buckets available on pk_new with full memory + do + { + int r; + caught = false; + expected_exit = false; + for (i = 0; i < PK_MAX_BUCKET_COUNT; ++i) { + pk_membucket *bkt = pk_bucket_create("lol2", 1024, false); + char *asdf = pk_new<char>(768, bkt); + (void)asdf; + } + r = setjmp(jmp_env); + if (r == 1) { + if (expected_exit == true && caught == true) { + expected_exit = false; + caught = false; + PK_LOGV_INF("%s: successfully caught err.\n", __FILE__); + fflush(stdout); + fflush(stderr); + break; + } else { + goto uncaught_err; + } + } + caught = false; + expected_exit = true; + char *asdf = pk_new<char>(768); + (void)asdf; + expected_exit = false; + goto uncaught_err; + } + while(false); + pk_memory_teardown_all(); + + // assert no buckets available on pk_new with full memory + do + { + int r; + caught = false; + expected_exit = false; + pk_membucket *bkt = nullptr; + for (i = 0; i < PK_MAX_BUCKET_COUNT; ++i) { + bkt = pk_bucket_create("lol3", 1024, false); + char *asdf = pk_new<char>(768, bkt); + (void)asdf; + } + r = setjmp(jmp_env); + if (r == 1) { + if (expected_exit == true && caught == true) { + expected_exit = false; + caught = false; + PK_LOGV_INF("%s: successfully caught err.\n", __FILE__); + fflush(stdout); + fflush(stderr); + break; + } else { + goto uncaught_err; + } + } + caught = false; + expected_exit = true; + char *asdf = pk_new<char>(768, bkt); + (void)asdf; + expected_exit = false; + goto uncaught_err; + } + while(false); + pk_memory_teardown_all(); return 0; +uncaught_err: + PK_LOGV_ERR("%s: failed to catch err.\n", __FILE__); + fflush(stdout); + fflush(stderr); + return 1; } |
