Compare commits

...

2 Commits

Author SHA1 Message Date
b753cfa761 Update README.md 2025-09-07 16:55:20 -07:00
2b59fff6a7 Remove floating refs 2025-09-07 16:54:57 -07:00
4 changed files with 32 additions and 80 deletions

View File

@ -4,10 +4,11 @@ RefCount is a reference counting (thus the name) and garbage collection library
written in C.
RefCount has support for
- breaking dependency chains via garbage collection
- Breaking dependency chains via garbage collection
- Multiple distinct contexts allowing the tracking of many types of objects,
each with a possibly different memory allocation implementation
- Safely running in multi-threaded environments
- Destructors that can possibly re-reference an object to save it
### Building

View File

@ -192,7 +192,6 @@ void *refcount_context_unref(RefcountContext *ctx, void *obj);
static inline void refcount_context_unref_as_callback(void *obj, void *ctx) {
refcount_context_unref(ctx, obj);
}
void *refcount_context_float(RefcountContext *ctx, void *obj);
ptrdiff_t refcount_context_garbage_collect(RefcountContext *ctx);
@ -320,15 +319,6 @@ static inline void refcount_unref_as_callback(void *obj) {
refcount_unref(obj);
}
/**
* Same as #refcount_context_float, but only operates on the global context.
* @param obj The object
* @return The input object
*/
static inline void *refcount_float(void *obj) {
return refcount_context_float(refcount_default_context, obj);
}
/**
* Same as #refcount_context_garbage_collect, but only operates on the globa
* context.

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.
*/

View File

@ -51,48 +51,45 @@ int main(int argc, const char **argv) {
A *a = make_a(c, 10, "Hello world\n");
assert(!refcount_context_is_static(c, a));
assert(refcount_context_num_refs(c, a) == 0);
a = refcount_context_ref(c, a);
assert(refcount_context_num_refs(c, a) == 1);
a = refcount_context_ref(c, a);
assert(refcount_context_num_refs(c, a) == 2);
a = refcount_context_ref(c, a);
assert(refcount_context_num_refs(c, a) == 3);
a = refcount_context_unref(c, a);
assert(refcount_context_num_refs(c, a) == 1);
assert(refcount_context_num_refs(c, a) == 2);
a = refcount_context_float(c, a);
assert(a);
assert(refcount_context_num_refs(c, a) == 0);
refcount_context_ref(c, a);
a = refcount_context_unref(c, a);
assert(refcount_context_num_refs(c, a) == 1);
a = refcount_context_unref(c, a);
assert(!a);
a = make_a(c, 10, "Hello World\n");
A *b = make_a(c, 42, "The answer!");
a->next = refcount_context_ref(c, b);
a->next = make_a(c, 42, "The answer!");
assert(refcount_context_num_refs(c, a->next) == 1);
assert(refcount_context_num_refs(c, a) == 0);
assert(refcount_context_num_refs(c, a) == 1);
refcount_context_unref(c, a);
a = make_a(c, 'a', "a");
a->next = refcount_context_ref(c, a);
assert(refcount_context_num_refs(c, a) == 1);
assert(refcount_context_num_refs(c, a) == 2);
refcount_context_unref(c, a);
assert(refcount_context_garbage_collect(c) == 1);
a = NULL;
for (char i = 'a'; i <= 'z'; ++i) {
A *na = make_a(c, i, "");
na->next = refcount_context_ref(c, a);
na->next = a;
a = na;
}
assert(refcount_context_num_refs(c, a) == 0);
assert(refcount_context_num_refs(c, a) == 1);
refcount_context_unref(c, a);
@ -103,11 +100,11 @@ int main(int argc, const char **argv) {
if (!first) {
first = na;
}
na->next = refcount_context_ref(c, a);
na->next = a;
a = na;
}
assert(refcount_context_num_refs(c, a) == 0);
first->next = refcount_context_ref(c, a);
assert(refcount_context_num_refs(c, a) == 1);
first->next = a;
a = first;
assert(refcount_context_num_refs(c, a) == 1);
@ -121,10 +118,10 @@ int main(int argc, const char **argv) {
a = NULL;
for (char i = 'a'; i <= 'z'; ++i) {
A *na = make_a(c, i, "");
na->next = refcount_context_ref(c, a);
na->next = a;
a = na;
}
assert(refcount_context_num_refs(c, a) == 0);
assert(refcount_context_num_refs(c, a) == 1);
refcount_context_unref(c, a);
assert(refcount_context_garbage_collect(c) == 0);
@ -136,14 +133,11 @@ int main(int argc, const char **argv) {
RefcountWeakref *w = refcount_context_make_weakref(c, a);
assert(w);
assert(refcount_context_num_refs(c, a) == 0);
b = refcount_context_strengthen(c, w);
assert(b);
assert(a == b);
assert(refcount_context_num_refs(c, a) == 1);
refcount_context_ref(c, a);
A *b = refcount_context_strengthen(c, w);
assert(b);
assert(a == b);
assert(refcount_context_num_refs(c, a) == 2);
RefcountWeakref *x = refcount_context_weaken(c, a);
@ -167,9 +161,9 @@ int main(int argc, const char **argv) {
refcount_context_destroy_weakref(c, x);
a = make_a(c, 10, "test destructor");
assert(refcount_context_num_refs(c, a) == 0);
assert(refcount_context_num_refs(c, a) == 1);
assert(refcount_context_add_destructor(c, a, &key, reref_destructor, c));
assert(refcount_context_num_refs(c, a) == 0);
assert(refcount_context_num_refs(c, a) == 1);
assert(!refcount_context_unref(c, a));
assert(refcount_context_num_refs(c, a) == 1);
assert(refcount_context_remove_destructor(c, a, &key));