Remove floating refs

This commit is contained in:
2025-09-07 16:54:57 -07:00
parent b40fcea2c2
commit 2b59fff6a7
3 changed files with 30 additions and 79 deletions

View File

@ -170,7 +170,9 @@ static bool init_obj_destructor_table(const RefcountContext *ctx, void *obj) {
}
/**
* Initialize the #RefcountEntry member of an object.
* Initialize the #RefcountEntry member of an object. After this call, the
* object will have a reference count of 1. Note that it is not safe to call
* this multiple times on the same 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
@ -182,7 +184,7 @@ 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->impl.counted.ref_count = 1;
if (!init_obj_destructor_table(ctx, obj)) {
return false;
}
@ -394,19 +396,14 @@ static void remove_gc_root(RefcountContext *ctx, void *obj) {
* @param ctx The #RefcountContext
* @param obj The object to unref
* @param queue A double pointer to a #RefcountList acting as a queue
* @param toplevel The toplevel object that triggered this unref. It is ignored
* so that it is not freed twice in the specific case that an object with a
* floating ref and a reference cycle is freed
* @return NULL if the reference count fell to 0, the given object otherwise
*/
static void *unref_to_queue(RefcountContext *ctx, void *obj,
RefcountList **queue, void *toplevel) {
RefcountList **queue) {
if (!obj) {
return NULL;
} else if (ENTRY->is_static) {
return obj;
} else if (obj == toplevel) {
return NULL;
}
if (!lock_entry_mtx(ENTRY)) {
// if this fails, we prefer a memory leak to causing undefined behavior
@ -437,7 +434,6 @@ static void *unref_to_queue(RefcountContext *ctx, void *obj,
*/
struct ContextAndQueue {
RefcountContext *ctx; //!< The context.
void *toplevel; //!< Toplevel object that triggered the unref.
RefcountList **queue; //!< The queue.
};
@ -448,8 +444,7 @@ struct ContextAndQueue {
*/
static void unref_to_queue_as_callback(void *obj, void *ctx_and_queue_raw) {
struct ContextAndQueue *ctx_and_queue = ctx_and_queue_raw;
unref_to_queue(ctx_and_queue->ctx, obj, ctx_and_queue->queue,
ctx_and_queue->toplevel);
unref_to_queue(ctx_and_queue->ctx, obj, ctx_and_queue->queue);
}
/**
@ -479,8 +474,7 @@ static inline void destroy_object(RefcountContext *ctx, void *obj) {
*/
static void process_unref_queue(RefcountContext *ctx, RefcountList *queue,
void *toplevel) {
struct ContextAndQueue ctx_and_queue = {
.ctx = ctx, .queue = &queue, .toplevel = toplevel};
struct ContextAndQueue ctx_and_queue = {.ctx = ctx, .queue = &queue};
while (queue) {
void *cur = refcount_list_peek(queue);
RefcountList *held_refs = NULL;
@ -514,38 +508,11 @@ void *refcount_context_unref(RefcountContext *ctx, void *obj) {
return NULL;
}
RefcountList *queue = NULL;
void *retval = unref_to_queue(ctx, obj, &queue, NULL);
void *retval = unref_to_queue(ctx, obj, &queue);
process_unref_queue(ctx, queue, obj);
return retval;
}
/**
* Float a reference of an object. That is, decrement it's refrerence count, but
* don't free it if the count drops to 0. It is safe to call this on a static
* object. It is an error to call this on an object that already has a floating
* reference.
* @param ctx The #RefcountContext
* @param obj The object
* @return The input object
*/
void *refcount_context_float(RefcountContext *ctx, void *obj) {
if (!obj) {
return NULL;
} else if (ENTRY->is_static) {
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;
}
/**
* Set of hash table functions used in #check_gc_root.
*/