Add destructors
This commit is contained in:
@ -4,6 +4,12 @@ set(REFCOUNT_USE_THREADS
|
||||
ON
|
||||
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)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
else()
|
||||
@ -22,7 +28,7 @@ include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
ht
|
||||
GIT_REPOSITORY https://git.zander.im/Zander671/ht.git
|
||||
GIT_TAG 9a1b271cfdc8ab203f9d6aa6a83cc4523de422be)
|
||||
GIT_TAG c6bdb38bb77d45f9d5083706723c84c37db56c9c)
|
||||
|
||||
FetchContent_MakeAvailable(ht)
|
||||
|
||||
@ -30,8 +36,8 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
||||
include(CTest)
|
||||
endif()
|
||||
|
||||
# add_compile_options(-fsanitize=address,leak,undefined)
|
||||
# add_link_options(-fsanitize=address,leak,undefined)
|
||||
add_compile_options(-fsanitize=address,leak,undefined)
|
||||
add_link_options(-fsanitize=address,leak,undefined)
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Structure holding the required information for a weak reference. That is, a
|
||||
* reference that does not prevent an object from being de-allocated.
|
||||
* Opaque structure holding the required information for a weak reference. That
|
||||
* is, a reference that does not prevent an object from being de-allocated.
|
||||
*/
|
||||
typedef struct RefcountWeakref {
|
||||
#ifdef REFCOUNT_HAS_THREADS
|
||||
@ -94,8 +94,18 @@ typedef struct RefcountWeakref {
|
||||
} RefcountWeakref;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Destructor callback type. Called before and object is freed after having its
|
||||
* 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
|
||||
* struct A {
|
||||
* int my_num;
|
||||
@ -106,7 +116,8 @@ typedef struct RefcountWeakref {
|
||||
*/
|
||||
typedef struct RefcountEntry {
|
||||
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 {
|
||||
struct {
|
||||
uint64_t ref_count; //!< The object's reference count.
|
||||
@ -226,6 +237,14 @@ static inline RefcountWeakref *refcount_context_weaken(RefcountContext *ctx,
|
||||
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
|
||||
* 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
|
||||
* @return The newly created weak reference, or NULL if an allocation error
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @param ctx The #RefcountContext
|
||||
@ -145,7 +183,13 @@ bool refcount_context_init_obj(const RefcountContext *ctx, void *obj) {
|
||||
ENTRY->is_static = false;
|
||||
ENTRY->impl.counted.gc_root = NULL;
|
||||
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;
|
||||
}
|
||||
@ -162,7 +206,11 @@ bool refcount_context_init_static(RefcountContext *ctx, void *obj) {
|
||||
}
|
||||
bool success = false;
|
||||
ENTRY->is_static = true;
|
||||
if (!init_obj_destructor_table(ctx, obj)) {
|
||||
goto end;
|
||||
}
|
||||
if (!init_obj_weakref(ctx, obj)) {
|
||||
refcount_free(&ctx->alloc, ENTRY->destructors);
|
||||
goto end;
|
||||
}
|
||||
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.
|
||||
* @param ctx The #RefcountContext
|
||||
@ -231,6 +313,8 @@ bool refcount_context_deinit_static(RefcountContext *ctx, void *obj) {
|
||||
goto end;
|
||||
}
|
||||
ENTRY->weak_ref->data = NULL;
|
||||
call_object_destructors(ctx, obj);
|
||||
ht_free(ENTRY->destructors);
|
||||
unref_weakref(ctx, ENTRY->weak_ref);
|
||||
unlock_entry_mtx(ENTRY);
|
||||
ctx->static_objects = refcount_list_remove_full(
|
||||
@ -330,7 +414,13 @@ static void *unref_to_queue(RefcountContext *ctx, void *obj,
|
||||
return obj;
|
||||
}
|
||||
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);
|
||||
return NULL;
|
||||
} else {
|
||||
@ -373,6 +463,7 @@ static inline void destroy_object(RefcountContext *ctx, void *obj) {
|
||||
}
|
||||
remove_gc_root(ctx, obj);
|
||||
ENTRY->weak_ref->data = NULL;
|
||||
ht_free(ENTRY->destructors);
|
||||
unlock_entry_mtx(ENTRY);
|
||||
unref_weakref(ctx, ENTRY->weak_ref);
|
||||
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 queue The queue
|
||||
* @param toplevel Toplevel object that triggered the unref
|
||||
@ -646,3 +737,50 @@ void *refcount_context_ref_weakref(const RefcountContext *ctx,
|
||||
unlock_mutex(&wr->mtx);
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
RefcountContext *c =
|
||||
refcount_make_context(offsetof(A, refcount), held_refs_callback,
|
||||
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");
|
||||
assert(!refcount_context_is_static(c, a));
|
||||
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, 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);
|
||||
|
||||
check_allocator_status();
|
||||
|
Reference in New Issue
Block a user