Add destructors
This commit is contained in:
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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user