Add multi-thread support
This commit is contained in:
288
src/refcount.c
288
src/refcount.c
@ -10,22 +10,31 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Callback to be executed when a program error occurred. A sane to default is
|
||||
* to print a message and then abort. This is not used to report normal
|
||||
* recoverable errors, but rather to report fatal logic errors.
|
||||
* Internal magic macro.
|
||||
*/
|
||||
refcount_debug_callback_t refcount_debug_breakpoint = NULL;
|
||||
#define ENTRY (REFCOUNT_OBJECT_ENTRY(ctx, obj))
|
||||
|
||||
/**
|
||||
* Call the breakpoint handler if one is installed.
|
||||
* @param oper The failing operation
|
||||
* @param ctx The #RefcountContext
|
||||
* @param obj The object on which the operation tried to act
|
||||
*/
|
||||
#define BREAKPOINT(oper, ctx, obj) \
|
||||
if (refcount_debug_breakpoint) { \
|
||||
refcount_debug_breakpoint(REFCOUNT_OPER_##oper, (ctx), (obj)); \
|
||||
}
|
||||
// Some utility macros for threads
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
# define lock_mutex(m) (mtx_lock(m) == thrd_success)
|
||||
# define unlock_mutex(m) (mtx_unlock(m))
|
||||
# define store_wr_count(obj, c) \
|
||||
atomic_store_explicit(obj, (c), memory_order_relaxed)
|
||||
# define inc_wr_count(obj) \
|
||||
(atomic_fetch_add_explicit(obj, 1, memory_order_relaxed))
|
||||
# define dec_wr_count(obj) \
|
||||
(atomic_fetch_sub_explicit(obj, 1, memory_order_relaxed))
|
||||
# define lock_entry_mtx(obj) (lock_mutex(&obj->weak_ref->mtx))
|
||||
# define unlock_entry_mtx(obj) (unlock_mutex(&obj->weak_ref->mtx))
|
||||
#else
|
||||
# define lock_mutex(m) true
|
||||
# define unlock_mutex(m) true
|
||||
# define store_wr_count(obj, c) (*(obj) = (c))
|
||||
# define inc_wr_count(obj) ((*(obj))++)
|
||||
# define dec_wr_count(obj) ((*(obj))--)
|
||||
# define lock_entry_mtx(obj) true
|
||||
# define unlock_entry_mtx(obj) true
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Default context to use for functions that do not take a context. You must
|
||||
@ -56,6 +65,17 @@ refcount_make_context(size_t entry_offset,
|
||||
if (!ctx) {
|
||||
return NULL;
|
||||
}
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
if (mtx_init(&ctx->so_mtx, mtx_plain) != thrd_success) {
|
||||
refcount_free(alloc, ctx);
|
||||
return NULL;
|
||||
}
|
||||
if (mtx_init(&ctx->gr_mtx, mtx_recursive) != thrd_success) {
|
||||
refcount_free(alloc, ctx);
|
||||
mtx_destroy(&ctx->so_mtx);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
ctx->entry_offset = entry_offset;
|
||||
ctx->held_refs_callback = held_refs_callback;
|
||||
ctx->destroy_callback = destroy_callback;
|
||||
@ -79,28 +99,55 @@ void refcount_context_destroy(RefcountContext *ctx) {
|
||||
ctx->static_objects, refcount_context_deinit_static_as_callback, ctx,
|
||||
&ctx->alloc);
|
||||
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
mtx_destroy(&ctx->so_mtx);
|
||||
mtx_destroy(&ctx->gr_mtx);
|
||||
#endif
|
||||
|
||||
refcount_context_garbage_collect(ctx);
|
||||
|
||||
refcount_free(&ctx->alloc, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal magic macro.
|
||||
* Initialize the weak_ref field on an object.
|
||||
* @param ctx The #RefcountContext
|
||||
* @param obj The object
|
||||
* @return True on success
|
||||
*/
|
||||
#define ENTRY (REFCOUNT_OBJECT_ENTRY(ctx, obj))
|
||||
static bool init_obj_weakref(const RefcountContext *ctx, void *obj) {
|
||||
ENTRY->weak_ref = refcount_malloc(&ctx->alloc, sizeof(RefcountWeakref));
|
||||
if (!ENTRY->weak_ref) {
|
||||
return false;
|
||||
}
|
||||
ENTRY->weak_ref->data = obj;
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
if (mtx_init(&ENTRY->weak_ref->mtx, mtx_recursive) != thrd_success) {
|
||||
refcount_free(&ctx->alloc, ENTRY->weak_ref);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
store_wr_count(&ENTRY->weak_ref->ref_count, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the #RefcountEntry member of an object.
|
||||
* @param ctx The #RefcountContext
|
||||
* @param obj The object to initialize
|
||||
* @return True on success, false on failure. Note that you don't have to do
|
||||
* anything special to clean up the #RefcountEntry structure on failure and it
|
||||
* is safe to call this a second time on the same object (though it will
|
||||
* probably fail for the same reason).
|
||||
*/
|
||||
void refcount_context_init_obj(const RefcountContext *ctx, void *obj) {
|
||||
bool refcount_context_init_obj(const RefcountContext *ctx, void *obj) {
|
||||
if (obj) {
|
||||
ENTRY->is_static = false;
|
||||
ENTRY->impl.counted.gc_root = NULL;
|
||||
ENTRY->impl.counted.ref_count = 0;
|
||||
ENTRY->weak_refs = NULL;
|
||||
init_obj_weakref(ctx, obj);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,15 +157,25 @@ void refcount_context_init_obj(const RefcountContext *ctx, void *obj) {
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool refcount_context_init_static(RefcountContext *ctx, void *obj) {
|
||||
ENTRY->is_static = true;
|
||||
ctx->static_objects =
|
||||
refcount_list_push_full(ctx->static_objects, obj, &ctx->alloc);
|
||||
if (!ctx->static_objects) {
|
||||
if (!lock_mutex(&ctx->so_mtx)) {
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
ENTRY->is_static = true;
|
||||
if (!init_obj_weakref(ctx, obj)) {
|
||||
goto end;
|
||||
}
|
||||
RefcountList *new_static_objects =
|
||||
refcount_list_push_full(ctx->static_objects, obj, &ctx->alloc);
|
||||
if (!new_static_objects) {
|
||||
goto end;
|
||||
}
|
||||
ctx->static_objects = new_static_objects;
|
||||
ENTRY->impl.static_entry = ctx->static_objects;
|
||||
ENTRY->weak_refs = NULL;
|
||||
return true;
|
||||
success = true;
|
||||
end:
|
||||
unlock_mutex(&ctx->so_mtx);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,24 +194,18 @@ static inline bool obj_held_refs(RefcountContext *ctx, void *obj,
|
||||
}
|
||||
|
||||
/**
|
||||
* Call back used from #clear_weak_references.
|
||||
* @param wr_raw The #RefcountWeakref object
|
||||
*/
|
||||
static void clear_weak_reference_callback(void *wr_raw) {
|
||||
RefcountWeakref *wr = wr_raw;
|
||||
wr->entry = NULL;
|
||||
wr->data = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all weak references of an object without freeing them.
|
||||
* Remove a reference from a weakref, possibly freeing it if it's reference
|
||||
* count falls to zero.
|
||||
* @param ctx The #RefcountContext
|
||||
* @param obj The object to clear
|
||||
* @param wr The weak reference
|
||||
*/
|
||||
static inline void clear_weak_references(const RefcountContext *ctx,
|
||||
void *obj) {
|
||||
refcount_list_free_full(ENTRY->weak_refs, clear_weak_reference_callback,
|
||||
&ctx->alloc);
|
||||
static void unref_weakref(const RefcountContext *ctx, RefcountWeakref *wr) {
|
||||
if (dec_wr_count(&wr->ref_count) == 1) {
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
mtx_destroy(&wr->mtx);
|
||||
#endif
|
||||
refcount_free(&ctx->alloc, wr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,21 +215,32 @@ static inline void clear_weak_references(const RefcountContext *ctx,
|
||||
* @return True on success, false otherwise
|
||||
*/
|
||||
bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
|
||||
if (refcount_context_is_static(ctx, obj)) {
|
||||
RefcountList *held_refs = NULL;
|
||||
if (!obj_held_refs(ctx, obj, &held_refs)) {
|
||||
return false;
|
||||
}
|
||||
clear_weak_references(ctx, obj);
|
||||
ctx->static_objects = refcount_list_remove_full(
|
||||
ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc);
|
||||
refcount_list_free_with_data_full(
|
||||
held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc);
|
||||
return true;
|
||||
} else {
|
||||
BREAKPOINT(DEINIT_STATIC, ctx, obj)
|
||||
if (!lock_mutex(&ctx->so_mtx)) {
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
if (!refcount_context_is_static(ctx, obj)) {
|
||||
goto end;
|
||||
}
|
||||
RefcountList *held_refs = NULL;
|
||||
if (!obj_held_refs(ctx, obj, &held_refs)) {
|
||||
goto end;
|
||||
}
|
||||
if (!lock_entry_mtx(ENTRY)) {
|
||||
refcount_list_free_full(held_refs, NULL, &ctx->alloc);
|
||||
goto end;
|
||||
}
|
||||
ENTRY->weak_ref->data = NULL;
|
||||
unref_weakref(ctx, ENTRY->weak_ref);
|
||||
unlock_entry_mtx(ENTRY);
|
||||
ctx->static_objects = refcount_list_remove_full(
|
||||
ctx->static_objects, ENTRY->impl.static_entry, NULL, &ctx->alloc);
|
||||
refcount_list_free_with_data_full(
|
||||
held_refs, refcount_context_unref_as_callback, ctx, &ctx->alloc);
|
||||
success = true;
|
||||
end:
|
||||
unlock_mutex(&ctx->so_mtx);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,9 +252,14 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
|
||||
void *refcount_context_ref(const RefcountContext *ctx, void *obj) {
|
||||
if (!obj) {
|
||||
return NULL;
|
||||
} else if (!ENTRY->is_static) {
|
||||
}
|
||||
if (!lock_entry_mtx(ENTRY)) {
|
||||
return obj;
|
||||
}
|
||||
if (!ENTRY->is_static) {
|
||||
++ENTRY->impl.counted.ref_count;
|
||||
}
|
||||
unlock_entry_mtx(ENTRY);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -204,15 +271,22 @@ void *refcount_context_ref(const RefcountContext *ctx, void *obj) {
|
||||
* @return True on success
|
||||
*/
|
||||
static bool track_gc_root(RefcountContext *ctx, void *obj) {
|
||||
if (!lock_mutex(&ctx->gr_mtx)) {
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
if (!ENTRY->impl.counted.gc_root) {
|
||||
ctx->gc_roots =
|
||||
refcount_list_push_full(ctx->gc_roots, obj, &ctx->alloc);
|
||||
if (!ctx->gc_roots) {
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
ENTRY->impl.counted.gc_root = ctx->gc_roots;
|
||||
}
|
||||
return true;
|
||||
success = true;
|
||||
end:
|
||||
unlock_mutex(&ctx->gr_mtx);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -222,9 +296,12 @@ static bool track_gc_root(RefcountContext *ctx, void *obj) {
|
||||
* @param obj The object to untrack
|
||||
*/
|
||||
static void remove_gc_root(RefcountContext *ctx, void *obj) {
|
||||
ctx->gc_roots = refcount_list_remove_full(
|
||||
ctx->gc_roots, ENTRY->impl.counted.gc_root, NULL, &ctx->alloc);
|
||||
ENTRY->impl.counted.gc_root = NULL;
|
||||
if (lock_mutex(&ctx->gr_mtx)) {
|
||||
ctx->gc_roots = refcount_list_remove_full(
|
||||
ctx->gc_roots, ENTRY->impl.counted.gc_root, NULL, &ctx->alloc);
|
||||
ENTRY->impl.counted.gc_root = NULL;
|
||||
unlock_mutex(&ctx->gr_mtx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,13 +323,22 @@ static void *unref_to_queue(RefcountContext *ctx, void *obj,
|
||||
return obj;
|
||||
} else if (obj == toplevel) {
|
||||
return NULL;
|
||||
} else if (ENTRY->impl.counted.ref_count <= 1) {
|
||||
*queue = refcount_list_push_full(*queue, obj, &ctx->alloc);
|
||||
return NULL;
|
||||
}
|
||||
--ENTRY->impl.counted.ref_count;
|
||||
track_gc_root(ctx, obj);
|
||||
return obj;
|
||||
if (!lock_entry_mtx(ENTRY)) {
|
||||
// if this fails, we prefer a memory leak to causing undefined behavior
|
||||
// and possibly crashing
|
||||
return obj;
|
||||
}
|
||||
if (ENTRY->impl.counted.ref_count <= 1) {
|
||||
*queue = refcount_list_push_full(*queue, obj, &ctx->alloc);
|
||||
unlock_entry_mtx(ENTRY);
|
||||
return NULL;
|
||||
} else {
|
||||
--ENTRY->impl.counted.ref_count;
|
||||
track_gc_root(ctx, obj);
|
||||
unlock_entry_mtx(ENTRY);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -282,8 +368,13 @@ static void unref_to_queue_as_callback(void *obj, void *ctx_and_queue_raw) {
|
||||
* @param obj The object to destroy
|
||||
*/
|
||||
static inline void destroy_object(RefcountContext *ctx, void *obj) {
|
||||
clear_weak_references(ctx, obj);
|
||||
if (!lock_entry_mtx(ENTRY)) {
|
||||
return;
|
||||
}
|
||||
remove_gc_root(ctx, obj);
|
||||
ENTRY->weak_ref->data = NULL;
|
||||
unlock_entry_mtx(ENTRY);
|
||||
unref_weakref(ctx, ENTRY->weak_ref);
|
||||
if (ctx->destroy_callback) {
|
||||
ctx->destroy_callback(obj, ctx->user_data);
|
||||
}
|
||||
@ -351,15 +442,16 @@ void *refcount_context_float(RefcountContext *ctx, void *obj) {
|
||||
return NULL;
|
||||
} else if (ENTRY->is_static) {
|
||||
return obj;
|
||||
} else if (!ENTRY->impl.counted.ref_count) {
|
||||
BREAKPOINT(FLOAT, ctx, obj);
|
||||
return obj;
|
||||
}
|
||||
if (!lock_entry_mtx(ENTRY)) {
|
||||
return NULL;
|
||||
}
|
||||
if (--ENTRY->impl.counted.ref_count) {
|
||||
track_gc_root(ctx, obj);
|
||||
} else {
|
||||
remove_gc_root(ctx, obj);
|
||||
}
|
||||
unlock_entry_mtx(ENTRY);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -475,6 +567,9 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) {
|
||||
// no loops possible
|
||||
return 0;
|
||||
}
|
||||
if (!lock_mutex(&ctx->gr_mtx)) {
|
||||
return -1;
|
||||
}
|
||||
ptrdiff_t total_cleared = 0;
|
||||
RefcountList *root = ctx->gc_roots;
|
||||
while (root) {
|
||||
@ -484,6 +579,7 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) {
|
||||
}
|
||||
total_cleared += res;
|
||||
}
|
||||
unlock_mutex(&ctx->gr_mtx);
|
||||
return total_cleared;
|
||||
}
|
||||
|
||||
@ -495,25 +591,12 @@ ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx) {
|
||||
* NULL to indicate that the referenced object is no longer in existence.
|
||||
* @param ctx The #RefcountContext
|
||||
* @param obj The object for which to create a weak reference
|
||||
* @return The newly created weak reference, or NULL if an allocated error
|
||||
* occurred.
|
||||
* @return The newly created weak reference
|
||||
*/
|
||||
RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx,
|
||||
void *obj) {
|
||||
RefcountWeakref *wr = refcount_malloc(&ctx->alloc, sizeof(RefcountWeakref));
|
||||
if (!wr) {
|
||||
return NULL;
|
||||
}
|
||||
RefcountList *new_weak_refs =
|
||||
refcount_list_push_full(ENTRY->weak_refs, wr, &ctx->alloc);
|
||||
if (!new_weak_refs) {
|
||||
refcount_free(&ctx->alloc, wr);
|
||||
return NULL;
|
||||
}
|
||||
ENTRY->weak_refs = new_weak_refs;
|
||||
wr->entry = new_weak_refs;
|
||||
wr->data = obj;
|
||||
return wr;
|
||||
inc_wr_count(&ENTRY->weak_ref->ref_count);
|
||||
return ENTRY->weak_ref;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -524,12 +607,24 @@ RefcountWeakref *refcount_context_make_weakref(const RefcountContext *ctx,
|
||||
*/
|
||||
void refcount_context_destroy_weakref(const RefcountContext *ctx,
|
||||
RefcountWeakref *wr) {
|
||||
if (wr->data) {
|
||||
void *obj = wr->data;
|
||||
ENTRY->weak_refs = refcount_list_remove_full(
|
||||
ENTRY->weak_refs, wr->entry, NULL, &ctx->alloc);
|
||||
unref_weakref(ctx, wr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return weather the object referenced by a weak reference still exists.
|
||||
* @param ctx The #RefcountContext
|
||||
* @param wr The weak reference
|
||||
* @return Weather the reference is still valid
|
||||
*/
|
||||
bool refcount_context_weakref_is_valid(const RefcountContext *ctx,
|
||||
RefcountWeakref *wr) {
|
||||
// we need the locks because accessing the data member is not atomic
|
||||
if (!lock_mutex(&wr->mtx)) {
|
||||
return NULL; // we can't be sure, so play it safe
|
||||
}
|
||||
refcount_free(&ctx->alloc, wr);
|
||||
bool is_valid = wr->data;
|
||||
unlock_mutex(&wr->mtx);
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -540,9 +635,14 @@ void refcount_context_destroy_weakref(const RefcountContext *ctx,
|
||||
* @return The newly referenced object, or NULL
|
||||
*/
|
||||
void *refcount_context_ref_weakref(const RefcountContext *ctx,
|
||||
const RefcountWeakref *wr) {
|
||||
if (!wr->data) {
|
||||
return NULL;
|
||||
RefcountWeakref *wr) {
|
||||
if (!lock_mutex(&wr->mtx)) {
|
||||
return NULL; // we can't be sure, so play it safe
|
||||
}
|
||||
return refcount_context_ref(ctx, wr->data);
|
||||
void *obj = NULL;
|
||||
if (wr->data) {
|
||||
obj = refcount_context_ref(ctx, wr->data);
|
||||
}
|
||||
unlock_mutex(&wr->mtx);
|
||||
return obj;
|
||||
}
|
||||
|
Reference in New Issue
Block a user