Add destructors
This commit is contained in:
@ -4,6 +4,12 @@ set(REFCOUNT_USE_THREADS
|
|||||||
ON
|
ON
|
||||||
CACHE BOOL "Weather or not RefCount should be thread aware.")
|
CACHE BOOL "Weather or not RefCount should be thread aware.")
|
||||||
|
|
||||||
|
if(REFCOUNT_USE_THREADS)
|
||||||
|
message("Building with thread support, setting CMAKE_C_STANDARD to 11.")
|
||||||
|
else()
|
||||||
|
message("Building without thread support, setting CMAKE_C_STANDARD to 99.")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(REFCOUNT_USE_THREADS)
|
if(REFCOUNT_USE_THREADS)
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
else()
|
else()
|
||||||
@ -22,7 +28,7 @@ include(FetchContent)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
ht
|
ht
|
||||||
GIT_REPOSITORY https://git.zander.im/Zander671/ht.git
|
GIT_REPOSITORY https://git.zander.im/Zander671/ht.git
|
||||||
GIT_TAG 9a1b271cfdc8ab203f9d6aa6a83cc4523de422be)
|
GIT_TAG c6bdb38bb77d45f9d5083706723c84c37db56c9c)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(ht)
|
FetchContent_MakeAvailable(ht)
|
||||||
|
|
||||||
@ -30,8 +36,8 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
|||||||
include(CTest)
|
include(CTest)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# add_compile_options(-fsanitize=address,leak,undefined)
|
add_compile_options(-fsanitize=address,leak,undefined)
|
||||||
# add_link_options(-fsanitize=address,leak,undefined)
|
add_link_options(-fsanitize=address,leak,undefined)
|
||||||
|
|
||||||
configure_file(include/refcount/config.h.in include/refcount/config.h @ONLY)
|
configure_file(include/refcount/config.h.in include/refcount/config.h @ONLY)
|
||||||
|
|
||||||
|
@ -80,8 +80,8 @@ refcount_make_context(size_t entry_offset,
|
|||||||
void refcount_context_destroy(RefcountContext *ctx);
|
void refcount_context_destroy(RefcountContext *ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Structure holding the required information for a weak reference. That is, a
|
* Opaque structure holding the required information for a weak reference. That
|
||||||
* reference that does not prevent an object from being de-allocated.
|
* is, a reference that does not prevent an object from being de-allocated.
|
||||||
*/
|
*/
|
||||||
typedef struct RefcountWeakref {
|
typedef struct RefcountWeakref {
|
||||||
#ifdef REFCOUNT_HAS_THREADS
|
#ifdef REFCOUNT_HAS_THREADS
|
||||||
@ -94,8 +94,18 @@ typedef struct RefcountWeakref {
|
|||||||
} RefcountWeakref;
|
} RefcountWeakref;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opaque struct holding reference data for an object. This should be included
|
* Destructor callback type. Called before and object is freed after having its
|
||||||
* as a non-pointer member of the struct you are trying to track, e.g.
|
* reference count drop to zero. This function can prevent the given object
|
||||||
|
* from being freed by increasing its reference count.
|
||||||
|
* @param obj The object being destroyed
|
||||||
|
* @param user_data Arbitrary user_provided data
|
||||||
|
*/
|
||||||
|
typedef void (*refcount_destructor_callback_t)(void *obj, void *user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opaque struct holding reference data for an object. This should be
|
||||||
|
* included as a non-pointer member of the struct you are trying to track,
|
||||||
|
* e.g.
|
||||||
* @code
|
* @code
|
||||||
* struct A {
|
* struct A {
|
||||||
* int my_num;
|
* int my_num;
|
||||||
@ -106,7 +116,8 @@ typedef struct RefcountWeakref {
|
|||||||
*/
|
*/
|
||||||
typedef struct RefcountEntry {
|
typedef struct RefcountEntry {
|
||||||
bool is_static; //!< Whether the object is static.
|
bool is_static; //!< Whether the object is static.
|
||||||
RefcountWeakref *weak_ref; //<! Weakref for this entry.
|
RefcountWeakref *weak_ref; //!< Weakref for this entry.
|
||||||
|
HTTable *destructors; //!< Hash table of object destructors.
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
uint64_t ref_count; //!< The object's reference count.
|
uint64_t ref_count; //!< The object's reference count.
|
||||||
@ -226,6 +237,14 @@ static inline RefcountWeakref *refcount_context_weaken(RefcountContext *ctx,
|
|||||||
return wr;
|
return wr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool refcount_context_add_destructor(const RefcountContext *ctx, void *obj,
|
||||||
|
void *key,
|
||||||
|
refcount_destructor_callback_t callback,
|
||||||
|
void *user_data);
|
||||||
|
|
||||||
|
bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj,
|
||||||
|
void *key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as #refcount_context_is_static, but only operates on the global
|
* Same as #refcount_context_is_static, but only operates on the global
|
||||||
* context.
|
* context.
|
||||||
@ -367,7 +386,7 @@ static inline void *refcount_strengthen(RefcountWeakref *wr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as #refcount_context_weaken, but operates ojn the global context.
|
* Same as #refcount_context_weaken, but operates on the global context.
|
||||||
* @param obj The object to create a weak reference for then unref
|
* @param obj The object to create a weak reference for then unref
|
||||||
* @return The newly created weak reference, or NULL if an allocation error
|
* @return The newly created weak reference, or NULL if an allocation error
|
||||||
* occurred. In this case the object is not unrefed.
|
* occurred. In this case the object is not unrefed.
|
||||||
@ -376,6 +395,35 @@ static inline RefcountWeakref *refcount_weaken(void *obj) {
|
|||||||
return refcount_context_weaken(refcount_default_context, obj);
|
return refcount_context_weaken(refcount_default_context, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as #refcount_context_add_destructor, but operates on the global context.
|
||||||
|
* @param obj The object onto which to register the destructor
|
||||||
|
* @param key An arbitrary value that can be later used to unregister the
|
||||||
|
* destructor
|
||||||
|
* @param callback The destructor itself
|
||||||
|
* @param user_data Extra data to pass to the destructor
|
||||||
|
* @return True on success, false on failure. On failure, nothing is registered.
|
||||||
|
*/
|
||||||
|
static inline bool
|
||||||
|
refcount_add_destructor(void *obj, void *key,
|
||||||
|
refcount_destructor_callback_t callback,
|
||||||
|
void *user_data) {
|
||||||
|
return refcount_context_add_destructor(refcount_default_context, obj, key,
|
||||||
|
callback, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as #refcount_context_remove_destructor, but operates on the global
|
||||||
|
* context.
|
||||||
|
* @param obj The object for which to unregister the destructor
|
||||||
|
* @param key The destructors key
|
||||||
|
* @return True on success, false on error. On error, nothing is unregistered.
|
||||||
|
*/
|
||||||
|
static inline bool refcount_remove_destructor(void *obj, void *key) {
|
||||||
|
return refcount_context_remove_destructor(refcount_default_context, obj,
|
||||||
|
key);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
144
src/refcount.c
144
src/refcount.c
@ -131,6 +131,44 @@ static bool init_obj_weakref(const RefcountContext *ctx, void *obj) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct DestructorEntry
|
||||||
|
* Hash table entry used to hold descructor callback information.
|
||||||
|
*/
|
||||||
|
struct DestructorEntry {
|
||||||
|
refcount_destructor_callback_t callback; //!< The callback itself.
|
||||||
|
void *user_data; //!< User specified data to pass to the callback.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free a #DestructorEntry using the given context.
|
||||||
|
* @param entry The entry to free
|
||||||
|
* @param ctx_raw The #RefcountContext
|
||||||
|
*/
|
||||||
|
static void free_destructor_entry_callback(void *entry, void *ctx_raw) {
|
||||||
|
const RefcountContext *ctx = ctx_raw;
|
||||||
|
refcount_free(&ctx->alloc, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the destructor table for an object.
|
||||||
|
* @param ctx The #RefcountContext
|
||||||
|
* @param obj The object to initialize
|
||||||
|
* @return True on success
|
||||||
|
*/
|
||||||
|
static bool init_obj_destructor_table(const RefcountContext *ctx, void *obj) {
|
||||||
|
ENTRY->destructors = ht_new(
|
||||||
|
&(HTTableFunctions) {
|
||||||
|
.equal = ht_intptr_equal_callback,
|
||||||
|
.hash = ht_intptr_hash_callback,
|
||||||
|
.destroy_key = NULL,
|
||||||
|
.destroy_value = free_destructor_entry_callback,
|
||||||
|
.user_data = (void *) ctx,
|
||||||
|
},
|
||||||
|
&ctx->ht_alloc, NULL);
|
||||||
|
return ENTRY->destructors;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the #RefcountEntry member of an object.
|
* Initialize the #RefcountEntry member of an object.
|
||||||
* @param ctx The #RefcountContext
|
* @param ctx The #RefcountContext
|
||||||
@ -145,7 +183,13 @@ bool refcount_context_init_obj(const RefcountContext *ctx, void *obj) {
|
|||||||
ENTRY->is_static = false;
|
ENTRY->is_static = false;
|
||||||
ENTRY->impl.counted.gc_root = NULL;
|
ENTRY->impl.counted.gc_root = NULL;
|
||||||
ENTRY->impl.counted.ref_count = 0;
|
ENTRY->impl.counted.ref_count = 0;
|
||||||
init_obj_weakref(ctx, obj);
|
if (!init_obj_destructor_table(ctx, obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!init_obj_weakref(ctx, obj)) {
|
||||||
|
ht_free(ENTRY->destructors);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -162,7 +206,11 @@ bool refcount_context_init_static(RefcountContext *ctx, void *obj) {
|
|||||||
}
|
}
|
||||||
bool success = false;
|
bool success = false;
|
||||||
ENTRY->is_static = true;
|
ENTRY->is_static = true;
|
||||||
|
if (!init_obj_destructor_table(ctx, obj)) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
if (!init_obj_weakref(ctx, obj)) {
|
if (!init_obj_weakref(ctx, obj)) {
|
||||||
|
refcount_free(&ctx->alloc, ENTRY->destructors);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
RefcountList *new_static_objects =
|
RefcountList *new_static_objects =
|
||||||
@ -208,6 +256,40 @@ static void unref_weakref(const RefcountContext *ctx, RefcountWeakref *wr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to pass two values to #call_object_destructors_foreach_callback.
|
||||||
|
*/
|
||||||
|
struct ContextAndObject {
|
||||||
|
const RefcountContext *ctx; //!< The #RefcountContext.
|
||||||
|
void *obj; //!< The object to check.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used from #call_object_destructors.
|
||||||
|
* @param key The hash table key
|
||||||
|
* @param entry_raw The #DestructorEntry
|
||||||
|
* @param ctx_and_obj_raw The #ContextAndObject
|
||||||
|
* @return True if the object's refcount is non-zero, false otherwise
|
||||||
|
*/
|
||||||
|
static bool call_object_destructors_foreach_callback(void *key, void *entry_raw,
|
||||||
|
void *ctx_and_obj_raw) {
|
||||||
|
struct ContextAndObject *ctx_and_obj = ctx_and_obj_raw;
|
||||||
|
const struct DestructorEntry *entry = entry_raw;
|
||||||
|
entry->callback(ctx_and_obj->obj, entry->user_data);
|
||||||
|
// if the refcount has increased past 0, stop looping
|
||||||
|
return refcount_context_num_refs(ctx_and_obj->ctx, ctx_and_obj->obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call descructors for an object.
|
||||||
|
* @param ctx The #RefcountContext
|
||||||
|
* @param obj The object to call descructors for
|
||||||
|
*/
|
||||||
|
static void call_object_destructors(const RefcountContext *ctx, void *obj) {
|
||||||
|
ht_foreach(ENTRY->destructors, call_object_destructors_foreach_callback,
|
||||||
|
&(struct ContextAndObject) {.ctx = ctx, .obj = obj});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister a static object in a context.
|
* Unregister a static object in a context.
|
||||||
* @param ctx The #RefcountContext
|
* @param ctx The #RefcountContext
|
||||||
@ -231,6 +313,8 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
ENTRY->weak_ref->data = NULL;
|
ENTRY->weak_ref->data = NULL;
|
||||||
|
call_object_destructors(ctx, obj);
|
||||||
|
ht_free(ENTRY->destructors);
|
||||||
unref_weakref(ctx, ENTRY->weak_ref);
|
unref_weakref(ctx, ENTRY->weak_ref);
|
||||||
unlock_entry_mtx(ENTRY);
|
unlock_entry_mtx(ENTRY);
|
||||||
ctx->static_objects = refcount_list_remove_full(
|
ctx->static_objects = refcount_list_remove_full(
|
||||||
@ -330,7 +414,13 @@ static void *unref_to_queue(RefcountContext *ctx, void *obj,
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
if (ENTRY->impl.counted.ref_count <= 1) {
|
if (ENTRY->impl.counted.ref_count <= 1) {
|
||||||
*queue = refcount_list_push_full(*queue, obj, &ctx->alloc);
|
ENTRY->impl.counted.ref_count = 0;
|
||||||
|
call_object_destructors(ctx, obj);
|
||||||
|
if (!ENTRY->impl.counted.ref_count) {
|
||||||
|
// if we still have no refs after calling destructors, it's really
|
||||||
|
// time to free this object
|
||||||
|
*queue = refcount_list_push_full(*queue, obj, &ctx->alloc);
|
||||||
|
}
|
||||||
unlock_entry_mtx(ENTRY);
|
unlock_entry_mtx(ENTRY);
|
||||||
return NULL;
|
return NULL;
|
||||||
} else {
|
} else {
|
||||||
@ -373,6 +463,7 @@ static inline void destroy_object(RefcountContext *ctx, void *obj) {
|
|||||||
}
|
}
|
||||||
remove_gc_root(ctx, obj);
|
remove_gc_root(ctx, obj);
|
||||||
ENTRY->weak_ref->data = NULL;
|
ENTRY->weak_ref->data = NULL;
|
||||||
|
ht_free(ENTRY->destructors);
|
||||||
unlock_entry_mtx(ENTRY);
|
unlock_entry_mtx(ENTRY);
|
||||||
unref_weakref(ctx, ENTRY->weak_ref);
|
unref_weakref(ctx, ENTRY->weak_ref);
|
||||||
if (ctx->destroy_callback) {
|
if (ctx->destroy_callback) {
|
||||||
@ -381,7 +472,7 @@ static inline void destroy_object(RefcountContext *ctx, void *obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Continually release held references and object held in a queue.
|
* Continually release held references and objects held in a queue.
|
||||||
* @param ctx The #RefcountContext
|
* @param ctx The #RefcountContext
|
||||||
* @param queue The queue
|
* @param queue The queue
|
||||||
* @param toplevel Toplevel object that triggered the unref
|
* @param toplevel Toplevel object that triggered the unref
|
||||||
@ -646,3 +737,50 @@ void *refcount_context_ref_weakref(const RefcountContext *ctx,
|
|||||||
unlock_mutex(&wr->mtx);
|
unlock_mutex(&wr->mtx);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a destructor to be called right before an object is freed. If the
|
||||||
|
* destructor adds a new reference to the object, the object is not freed. The
|
||||||
|
* destructor will then be run again the next time the object is about to be
|
||||||
|
* freed.
|
||||||
|
*
|
||||||
|
* Note that if a destructor already exists for the given key, it will be
|
||||||
|
* replaced without calling it.
|
||||||
|
* @param ctx The #RefcountContext
|
||||||
|
* @param obj The object onto which to register the destructor
|
||||||
|
* @param key An arbitrary value that can be later used to unregister the
|
||||||
|
* destructor
|
||||||
|
* @param callback The destructor itself
|
||||||
|
* @param user_data Extra data to pass to the destructor
|
||||||
|
* @return True on success, false on failure. On failure, nothing is registered.
|
||||||
|
*/
|
||||||
|
bool refcount_context_add_destructor(const RefcountContext *ctx, void *obj,
|
||||||
|
void *key,
|
||||||
|
refcount_destructor_callback_t callback,
|
||||||
|
void *user_data) {
|
||||||
|
struct DestructorEntry *entry =
|
||||||
|
refcount_malloc(&ctx->alloc, sizeof(struct DestructorEntry));
|
||||||
|
if (!entry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entry->callback = callback;
|
||||||
|
entry->user_data = user_data;
|
||||||
|
if (!ht_insert(ENTRY->destructors, key, entry)) {
|
||||||
|
refcount_free(&ctx->alloc, entry);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a destructor from the given object. The destructor will not be
|
||||||
|
* called. If no destructor exists for the given key, this will do nothing.
|
||||||
|
* @param ctx The #RefcountContext
|
||||||
|
* @param obj The object for which to unregister the destructor
|
||||||
|
* @param key The destructors key
|
||||||
|
* @return True on success, false on error. On error, nothing is unregistered.
|
||||||
|
*/
|
||||||
|
bool refcount_context_remove_destructor(const RefcountContext *ctx, void *obj,
|
||||||
|
void *key) {
|
||||||
|
return ht_remove(ENTRY->destructors, key);
|
||||||
|
}
|
||||||
|
@ -32,11 +32,23 @@ void destroy_callback(void *a_raw, void *ignored) {
|
|||||||
counting_free(a);
|
counting_free(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reref_destructor(void *a, void *ctx_raw) {
|
||||||
|
const RefcountContext *ctx = ctx_raw;
|
||||||
|
refcount_context_ref(ctx, a);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, const char **argv) {
|
int main(int argc, const char **argv) {
|
||||||
RefcountContext *c =
|
RefcountContext *c =
|
||||||
refcount_make_context(offsetof(A, refcount), held_refs_callback,
|
refcount_make_context(offsetof(A, refcount), held_refs_callback,
|
||||||
destroy_callback, NULL, &COUNTING_ALLOCATOR);
|
destroy_callback, NULL, &COUNTING_ALLOCATOR);
|
||||||
|
|
||||||
|
A static_a = {
|
||||||
|
.num = 0,
|
||||||
|
.str = counting_strdup("static"),
|
||||||
|
.next = make_a(c, 0, "in static"),
|
||||||
|
};
|
||||||
|
refcount_context_init_static(c, &static_a);
|
||||||
|
|
||||||
A *a = make_a(c, 10, "Hello world\n");
|
A *a = make_a(c, 10, "Hello world\n");
|
||||||
assert(!refcount_context_is_static(c, a));
|
assert(!refcount_context_is_static(c, a));
|
||||||
assert(refcount_context_num_refs(c, a) == 0);
|
assert(refcount_context_num_refs(c, a) == 0);
|
||||||
@ -148,6 +160,20 @@ int main(int argc, const char **argv) {
|
|||||||
refcount_context_destroy_weakref(c, w);
|
refcount_context_destroy_weakref(c, w);
|
||||||
refcount_context_destroy_weakref(c, x);
|
refcount_context_destroy_weakref(c, x);
|
||||||
|
|
||||||
|
a = make_a(c, 10, "test destructor");
|
||||||
|
assert(refcount_context_num_refs(c, a) == 0);
|
||||||
|
int key;
|
||||||
|
assert(refcount_context_add_destructor(c, a, &key, reref_destructor, c));
|
||||||
|
assert(refcount_context_num_refs(c, a) == 0);
|
||||||
|
assert(!refcount_context_unref(c, a));
|
||||||
|
assert(refcount_context_num_refs(c, a) == 1);
|
||||||
|
assert(refcount_context_remove_destructor(c, a, &key));
|
||||||
|
assert(refcount_context_num_refs(c, a) == 1);
|
||||||
|
assert(!refcount_context_unref(c, a));
|
||||||
|
|
||||||
|
refcount_context_deinit_static(c, &static_a);
|
||||||
|
counting_free(static_a.str);
|
||||||
|
|
||||||
refcount_context_destroy(c);
|
refcount_context_destroy(c);
|
||||||
|
|
||||||
check_allocator_status();
|
check_allocator_status();
|
||||||
|
Reference in New Issue
Block a user